egoist / tsup

The simplest and fastest way to bundle your TypeScript libraries.
https://tsup.egoist.dev
MIT License
9.32k stars 225 forks source link

Declaration files don't contain triple slash types directives #977

Open kasperpeulen opened 1 year ago

kasperpeulen commented 1 year ago

We are using tsup in storybook, but we seem to have a problem with the declaration files emitted by tsup.

This is a minimal reproduction of the file:

import { default as expectPatched } from '@storybook/expect';

export interface Expect extends Pick<jest.Expect, keyof jest.Expect> {
  <T = any>(actual: T): jest.JestMatchersShape<
    jest.Matchers<Promise<void>, T>,
    jest.Matchers<Promise<void>, T>
  >;
}

export const expect: Expect = expectPatched as Expect;

When I just try to generate type declaration with tsc, it will output this:

/// <reference types="jest" />
export interface Expect extends Pick<jest.Expect, keyof jest.Expect> {
    <T = any>(actual: T): jest.JestMatchersShape<jest.Matchers<Promise<void>, T>, jest.Matchers<Promise<void>, T>>;
}
export declare const expect: Expect;

But when I do same with tsup, the triple slash types directive is missing:

interface Expect extends Pick<jest.Expect, keyof jest.Expect> {
    <T = any>(actual: T): jest.JestMatchersShape<jest.Matchers<Promise<void>, T>, jest.Matchers<Promise<void>, T>>;
}
declare const expect: Expect;

export { Expect, expect };

Can I somehow configure tsup to generate those directives. It seems like this is meant to be included:

https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-

For declaration files generated during compilation, the compiler will automatically add /// <reference types="..." /> for you; A /// <reference types="..." /> in a generated declaration file is added if and only if the resulting file uses any declarations from the referenced package.

This also breaks when building storybook in angular projects. When I manually add the directive it works again:

> yarn build-storybook --quiet
info => Cleaning outputDir: /storybook-static
info => Loading presets
info => Building manager..
info => Manager built (369 ms)
info => Compiling preview..
info => Copying static files: /tmp/storybook/sandbox/angular-cli-default-ts/node_modules/@storybook/manager/static at /tmp/storybook/sandbox/angular-cli-default-ts/storybook-static/sb-common-assets
info Addon-docs: using MDX2
info => Using implicit CSS loaders
info => Using angular browser target options from "angular-latest:build"
info => Using angular project with "tsConfig:/tmp/storybook/sandbox/angular-cli-default-ts/.storybook/tsconfig.json"
info => Using default Webpack5 setup
ERR! => Failed to build the preview
ERR! node_modules/@storybook/jest/dist/index.d.ts:8:31 - error TS2503: Cannot find namespace 'jest'.
ERR! 
ERR! 8 interface Expect extends Pick<jest.Expect, keyof jest.Expect> {
ERR!                                 ~~~~
ERR! 
ERR! node_modules/@storybook/jest/dist/index.d.ts:8:50 - error TS2503: Cannot find namespace 'jest'.
ERR! 
ERR! 8 interface Expect extends Pick<jest.Expect, keyof jest.Expect> {
ERR!                                                    ~~~~
ERR! 
ERR! node_modules/@storybook/jest/dist/index.d.ts:15:27 - error TS2503: Cannot find namespace 'jest'.
ERR! 
ERR! 15     <T = any>(actual: T): jest.JestMatchersShape<jest.Matchers<Promise<void>, T>, jest.Matchers<Promise<void>, T>>;
ERR!                              ~~~~
ERR! 
ERR! node_modules/@storybook/jest/dist/index.d.ts:15:50 - error TS2503: Cannot find namespace 'jest'.
ERR! 
ERR! 15     <T = any>(actual: T): jest.JestMatchersShape<jest.Matchers<Promise<void>, T>, jest.Matchers<Promise<void>, T>>;
ERR!                                                     ~~~~
ERR! 
ERR! node_modules/@storybook/jest/dist/index.d.ts:15:83 - error TS2503: Cannot find namespace 'jest'.
ERR! 
ERR! 15     <T = any>(actual: T): jest.JestMatchersShape<jest.Matchers<Promise<void>, T>, jest.Matchers<Promise<void>, T>>;
ERR!      

One workaround I found is setting the banner:

  dts: {
    entry: ['./src/index.ts'],
    resolve: true,
    banner: '/// <reference types="jest" />',
  },

Upvote & Fund

Fund with Polar

maxpatiiuk commented 3 months ago

We were able to workaround this by using tsc for types generation:

// tsup.config.ts
import { defineConfig } from "tsup";

export default defineConfig({
  entry: ["src/index.ts"],
  format: ["esm"],
  onSuccess: "yarn tsc -p tsconfig.tsup.json && echo 'Rebuilt'",
});
// tsconfig.tsup.json
{
  "$schema": "https://json.schemastore.org/tsconfig",
  "extends": "./tsconfig.json",
  "include": ["src"],
  "compilerOptions": {
    "noEmit": false,
    "emitDeclarationOnly": true,
    "outDir": "dist",
    "rootDir": "src"
  }
}

That also fixes https://github.com/egoist/tsup/issues/1050

maxpatiiuk commented 3 months ago

Independent of tsup, beware of this change in TypeScript 5.5 regarding reference directives: https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/

You need to add preserve="true" to the directives if you want them to be preserved in the output

milichev commented 3 weeks ago

@maxpatiiuk, it seems that tsup does not respect preserve="true". When /// <reference types="jest" preserve="true" /> is added to the tsup entry src/index.ts file, it is stripped from the resulting .d.ts file.