unplugin-dts icon indicating copy to clipboard operation
unplugin-dts copied to clipboard

Duplicate definition of types when multiple entrypoints

Open Kegulf opened this issue 1 year ago • 7 comments

Describe the bug

I have a project where I have two entrypoints, one is for the base functionality, one is for react specific functionality.

  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      }
    },
    "./react": {
      "import": {
        "types": "./dist/react.d.ts",
        "default": "./dist/react.js"
      }
    }
  },

vite-plugin-dts handles two entrypoints quite well, and creates seperate d.ts files for both index.js and react.js.

The problem is they use common functionality, Vite detects this and extract this functionality into a seperate file, so both entrypoints can import from it. vite-plugin-dts takes a different approach to solve this problem; it inserts the typings in both .d.ts files instead of creating this "CommonCode" file. This leads to two definitions of several of the types, and leads to some interesting type mismatch bugs.

I only discovered this bug because I had a class in the common code, which defines private readonly some_value.

image

Reproduction

https://github.com/Kegulf/vitejs-vite-jbxngt

Steps to reproduce

  • Create package being bundled in lib mode by vite with two entrypoints ( See vite.config.ts below )
  • Add Entrypoint definitions to package.json (see section "In package.json" below)
  • Create three files in source, two is entrypoints, one is common code used by both entrypoints.
    • Make sure to export the code using the common code from both entrypoints.

In package.json

  "type": "module",
  "module": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/index.js"
      }
    },
    "./react": {
      "import": {
        "types": "./dist/react.d.ts",
        "default": "./dist/react.js"
      }
    }
  },


vite.config.ts

import { resolve } from "node:path";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import dts from "vite-plugin-dts";
import packageJson from "./package.json";
import tsconfigJson from "./tsconfig.json";

export default defineConfig({
  plugins: [react(), dts({ rollupTypes: true })],
  build: {
    sourcemap: true,
    target: tsconfigJson.compilerOptions.target,
    lib: {
      entry: {
        index: resolve(__dirname, "src/index.ts"),
        react: resolve(__dirname, "src/react/index.ts"),
      },
      formats: ["es"],
    },
    rollupOptions: {
      external: [
        ...Object.keys(packageJson.peerDependencies),
        "react/jsx-runtime",
      ],
    },
  },
});

System Info

System:
    OS: Windows 11 10.0.22621
    CPU: (20) x64 12th Gen Intel(R) Core(TM) i7-12700H
    Memory: 9.38 GB / 31.68 GB
  Binaries:
    Node: 20.9.0 - C:\Program Files\nodejs\node.EXE
    npm: 10.2.3 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Edge: Chromium (123.0.2420.97)
    Internet Explorer: 11.0.22621.1
  npmPackages:
    @vitejs/plugin-react: ^4.2.1 => 4.2.1
    vite: ^5.2.9 => 5.2.9
    vite-plugin-dts: ^3.8.3 => 3.8.3

Validations

Kegulf avatar Apr 19 '24 10:04 Kegulf

I see that before the rollupTypes the someCommonCode.d.ts actually exists, which is fascinating 😁 It might be possible to check if the contents of the files is used by several entrypoints, and avoid merging it into the rolled up typefiles? image

Kegulf avatar Apr 19 '24 10:04 Kegulf

Currently this is hard to implement.

The rollup process is powered by @microsoft/api-extractor and it's difficult (probably noway) to change its behavior.

qmhc avatar Apr 23 '24 05:04 qmhc

I see, I don't have any suggestions on how to fix this without a rewrite, unless ms-api-extractor can be configured to fix it.

Kegulf avatar Apr 25 '24 10:04 Kegulf

I just ran in to a very similar problem with symbols being duplicated between entrypoints.

With multiple entrypoints and rolled up types, I would still expect a common .d.ts file extracted that all entrypoint .d.ts files can import from so that the unique symbol behavior that tsc enforces works correctly between the multiple entrypoints. Today, every usage of a unique symbol in each entrypoint is treated as a totally different unique symbol in the .d.ts output :(

NullVoxPopuli avatar Apr 30 '24 20:04 NullVoxPopuli

I also ran into the same problem. Any work arounds?

lekhnath avatar Aug 12 '24 05:08 lekhnath

Maybe you can try to build common types lib, and other packages import shared types from this lib.

qmhc avatar Sep 25 '24 10:09 qmhc

I have managed to make this work with tsup. Have tsup produce dts files then move them over to dist files produced by vite. (For my case, I want separate .js and .d.ts files for each component in dist folder.)

tsup --dts-only && mv types/* dist && rm -rf types

tsup.config.ts :

import { defineConfig } from 'tsup';
import { globSync } from 'glob';
import { resolve } from 'path';

const components = globSync('src/components/*/index.ts').map(path =>
  resolve(__dirname, path),
);

const entry = components.reduce((acc, path) => {
  const componentName = path.split('/').at(-2);
  acc[componentName!] = path;
  return acc;
}, {});

export default defineConfig({
  dts: true,
  sourcemap: true,
  outDir: 'types',
  format: ['esm'],
  entry,
  loader: {
    '.scss': 'text',
  },
});

beratbayram avatar Sep 25 '24 13:09 beratbayram