-
Notifications
You must be signed in to change notification settings - Fork 87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generate different declaration types #267
Comments
I have read the information you provided, but I can't find the difference in content between |
I have equally had the same questions but have found little information on what the true differences actually are syntactically. Sadly, the js ecosystem is really weird and this question is very obscure. The only differences I can think of are the differences with how things are exported, cjs exports map to The original issue was found when looking at https://publint.dev/rules#export_types_invalid_format So, it is quite possible that these things are the same. But there isn't a whole lot of info. In my opinion, they are likely the same. But perhaps this is a deeper question to TypeScript as there isn't a whole lot of information on this topic. Indeed this https://arethetypeswrong.github.io/?p=bootstrap-vue-next%400.12.0 displays that some of the issue described contain these resolution issues |
I found this in TypeScript docs: https://www.typescriptlang.org/docs/handbook/esm-node.html#packagejson-exports-imports-and-self-referencing. It says: It’s important to note that the CommonJS entrypoint and the ES module entrypoint each needs its own declaration file, even if the contents are the same between them. And I have never seen a declaration file uses So, currently we can consider they are only different in their file extensions. For this, we can add some options for custom the declaration file extensions (while considering multiple outputs with different file extensions). |
This was a mistake. I meant to account for the differences between export default/export {} & module.exports.
This will likely work. If this is implemented, I would like to check and see what arethetypeswrong.github.io says. If it comes out with everything being clean, then imo there should likely be a section on this projects README as it should become the standard |
Hello, facing the exact same issue. Any updates so far? |
The way we've handled this in our company's internal component library, is to use the import { copyFileSync } from "node:fs"
import { defineConfig } from "vite"
import dts from "vite-plugin-dts"
// https://vitejs.dev/config/
export default defineConfig({
build: {...},
plugins: [
...,
dts({
afterBuild: () => {
// To pass publint (`npm x publint@latest`) and ensure the
// package is supported by all consumers, we must export types that are
// read as ESM. To do this, there must be duplicate types with the
// correct extension supplied in the package.json exports field.
copyFileSync("dist/index.d.ts", "dist/index.d.mts")
},
exclude: [ ... ],
include: ["src"],
}),
],
}) |
One small change to make publint happy. Change to
Specifically, the CJS types need to use the |
@mkilpatrick yes! I had to do that version when converting to proper esm-first. |
If I only have a default export Or as an odd workaround, if I add an extra export, next to the default export, then the error also goes away in |
I handle my types slightly different than approaches above. I have separate
{
"compilerOptions": {
"declaration": true,
"declarationMap": true
}
}
import { defineConfig } from 'vite';
import fs from 'fs-extra';
import dts from 'vite-plugin-dts';
import { glob } from 'glob';
export default defineConfig({
plugins: [
dts({
outDir: ['dist/types/esm', 'dist/types/cjs'],
afterBuild: async () => {
const files = glob.sync('dist/types/cjs/**/*.d.{ts,ts.map}', { nodir: true });
await Promise.all(
files.map(async (file) => {
const newFilePath = file.replace(/\.d\.ts(\.map)?$/, '.c.ts$1');
await fs.move(file, newFilePath, { overwrite: true });
}),
);
},
}),
];
})
{
"exports": {
".": {
"import": {
"types": "./dist/types/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/types/cjs/index.c.ts",
"default": "./dist/cjs/index.cjs"
}
},
"./*": {
"import": {
"types": "./dist/types/esm/*",
"default": "./dist/esm/*"
},
"require": {
"types": "./dist/types/cjs/*",
"default": "./dist/cjs/*"
}
}
}
} |
Update: After several failed attempts, I found a way to use this plugin to generate valid esm and cjs
Using the examples above, I had to do a few extra things with this plugin to ensure the audit passes:
Here is what my new config looks like:
"exports": {
".": {
"import": {
"types": "./dist/types/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/types/cjs/index.d.cts",
"default": "./dist/cjs/index.cjs"
}
},
"./*": {
"import": {
"types": "./dist/types/esm/*",
"default": "./dist/esm/*"
},
"require": {
"types": "./dist/types/cjs/*",
"default": "./dist/cjs/*"
}
},
"./styles/*": {
"import": "./dist/styles/*",
"require": "./dist/styles/*"
}
}
dts({
// create two type folders, one for esm and cjs
outDir: ['dist/types/esm', 'dist/types/cjs'],
// modify type files after they have been written
afterBuild: async () => {
// Fetch all .d.ts files recursively from the dist/types/cjs directory
const files = glob.sync('dist/types/cjs/**/*.d.{ts,ts.map}', { nodir: true });
// Since TypeScript 5.0, it has emphasized that type files (*.d.ts) are also affected by its ESM and CJS context.
// This means that you can't share a single type file for both ESM and CJS exports of your library.
// You need to have two type files when dual-publishing your library.
// see https://www.typescriptlang.org/docs/handbook/modules/reference.html#node16-nodenext and
// https://publint.dev/rules#export_types_invalid_format
await Promise.all(
// Ideally, this plugin will support different types in the future
// See https://github.com/qmhc/vite-plugin-dts/issues/267
files.map(async (file) => {
// Generate the new files with the new .c.ts/.c.ts.map naming
const newFilePath = file.replace(/\.d\.ts(\.map)?$/, '.d.cts$1');
await fs.move(file, newFilePath, { overwrite: true });
// Update sourceMappingURL references
if (newFilePath.endsWith('.d.cts')) {
const content = await fs.readFile(newFilePath, 'utf-8');
let updatedContent = content.replace(/\/\/# sourceMappingURL=.*\.d\.ts\.map/g, (match) =>
match.replace('.d.ts.map', '.d.cts.map'),
);
// Update .js references to .cjs
updatedContent = updatedContent.replace(/(from\s+['"].*?)\.js(['"])/g, '$1.cjs$2');
await fs.writeFile(newFilePath, updatedContent, 'utf-8');
}
// Update source map file references
if (newFilePath.endsWith('.d.cts.map')) {
const content = await fs.readJson(newFilePath);
content.file = content.file.replace('.d.ts', '.d.cts');
await fs.writeJson(newFilePath, content);
}
}),
);
},
}), I haven't tested this fully yet with cjs and my subpath wildcards, but I think manually updating the Ideally this plugin should be updated to reflect Typescript 5's new guidelines. All that would be needed is the option to specify different extensions and have this plugin reflect those extension names during the generation process so we don't have to do it manually here. |
Description
One of the differences introduced by TypeScript v5 is that .d.ts files are affected by esm and cjs contexts. So, sharing a single .d.ts file between the two can lead to invalid formats when a library is dual publishing for these contexts.
The solution is to generate .d.mts & .d.cjs files.
https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseCJS.md
https://github.com/arethetypeswrong/arethetypeswrong.github.io/blob/main/docs/problems/FalseESM.md
Under what circumstance would Vite create these wrong, I am not sure. However, for integrity, it would be best for these to be split up.
Suggested solution
Create an option to generate different, or multiple versions of the output files. It's not necessarily a requirement for the library to add specific support to generate .d.mts, .d.cts variants, but the ability to do it yourself would work.
Perhaps this is already possible through one of the included hooks. However, this I am not sure of.
Alternative
I don't really think there is an alternative.
Additional context
Alternatively/additionally, a section on the FAQ could also help.
This could also be a vue-tsc related issue, as I don't think they generate those files.
Validations
The text was updated successfully, but these errors were encountered: