Duplicate definition of types when multiple entrypoints
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.
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
- [X] Read the FAQ.
- [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- [X] The provided reproduction is a minimal reproducible example of the bug.
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?
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.
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.
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 :(
I also ran into the same problem. Any work arounds?
Maybe you can try to build common types lib, and other packages import shared types from this lib.
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',
},
});