Skip to content
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

Open
2 tasks done
VividLemon opened this issue Sep 5, 2023 · 10 comments
Open
2 tasks done

Generate different declaration types #267

VividLemon opened this issue Sep 5, 2023 · 10 comments
Labels
enhancement New feature or request

Comments

@VividLemon
Copy link

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

  • Read the FAQ.
  • Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
@qmhc
Copy link
Owner

qmhc commented Sep 6, 2023

I have read the information you provided, but I can't find the difference in content between .d.cts and .d.mts files. Are they just different file extensions?

@VividLemon
Copy link
Author

VividLemon commented Sep 6, 2023

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 export =, mjs exports map to export {} or export default

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

@qmhc
Copy link
Owner

qmhc commented Sep 6, 2023

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 export =.

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).

@VividLemon
Copy link
Author

And I have never seen a declaration file uses export =.

This was a mistake. I meant to account for the differences between export default/export {} & module.exports.

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 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

@BibiSebi
Copy link

Hello, facing the exact same issue. Any updates so far?

@will-stone
Copy link

The way we've handled this in our company's internal component library, is to use the afterBuild hook to copy and rename the files:

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"],
    }),
  ],
})

@mkilpatrick
Copy link

One small change to make publint happy. Change to copyFileSync("dist/index.d.ts", "dist/index.d.cts"); and have your package.json exports look something like:

"exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "default": "./dist/main.js"
      },
      "require": {
        "types": "./dist/index.d.cts",
        "default": "./dist/main.cjs"
      }
    }
  },

Specifically, the CJS types need to use the .cts extension rather than having a .mts extension for ESM, at least when you're using "type": "module".

@will-stone
Copy link

@mkilpatrick yes! I had to do that version when converting to proper esm-first.

@Juice10
Copy link

Juice10 commented Apr 16, 2024

And I have never seen a declaration file uses export =.

If I only have a default export attw complains about node16 (from CJS) │ ❗️ Incorrect default export. If I modify the .d.cts file's export default MyDefaultExportedFn to export = MyDefaultExportedFn then the error goes away.

Or as an odd workaround, if I add an extra export, next to the default export, then the error also goes away in attw

@waldronmatt
Copy link

I handle my types slightly different than approaches above. I have separate esm and cjs folders via dist/types/esm/ and dist/types/cjs/. This is my approach to generate esm and cjs typings with the correct file extensions for both .d.ts and .d.ts.map files:

tsconfig.json

{
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true
  }
}

vite.config.ts

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 });
          }),
        );
      },
    }),
  ];
})

package.json

{
 "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/*"
      }
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants