microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
101.01k stars 12.48k forks source link

JSDoc-typed node modules require special configuration in consumers to be useful #19145

Open akdor1154 opened 7 years ago

akdor1154 commented 7 years ago

It looks like an ongoing goal for TS is to recommend JSDoc as the recommended way to give type-checking to raw JS users. As a user I am not a huge fan of this approach but I'll try to be objective in this issue.

If I write a node module js-lib as raw javascript typed with jsdoc, it will appear as untyped to consumers of my module unless they specifically opt in to JS type checking.

Example:

+-js-lib
| +-index.js
| +-package.json
|
+-ts-consumer
  +-index.ts
  +-package.json
  +-tsconfig.json

js-lib/index.js

/**
 * @param {string} s
 * @returns {number}
 */
module.exports = function test(s) { return parseInt(s) };

js-lib/package.json

{
  "name": "js-lib",
  "main": "index.js"
}

ts-consumer/index.ts

import test = require('../js-lib');
// index.ts(1,23): error TS6143: Module '../js-lib' was resolved to '/Users/jarrad/src/personal/ts-test/lib/js-index.js', but '--allowJs' is not set.

import test = require('js-lib'); // after npm install ../js-lib
// index.ts(1,23): error TS7016: Could not find a declaration file for module 'lib'. '/Users/jarrad/src/personal/ts-test/lib/index.js' implicitly has an 'any' type.
//  Try `npm install @types/lib` if it exists or add a new declaration (.d.ts) file containing `declare module 'lib';

test("test");

ts-consumer/tsconfig.json

{
  "compilerOptions": {
    "strict": true
  }
}

It should be possible for me to make my js-lib usable to Typescript authors without them having to configure their own project with allowJs and checkJs.

DanielRosenwasser commented 7 years ago

Sounds like this could be solved by #7546.

akdor1154 commented 7 years ago

Thanks for the reply, though perhaps that's more of a workaround (though not an awful one).

A reason I am using typescript-via-JSDoc is to avoid a build/compilation step (organizational requirements, and I don't know how pervasive this use-case is). If I'm going to need a compilation step anyway to allow people to use my definitions then I lose this benefit.

DanielRosenwasser commented 7 years ago

Well there's maxNodeModuleJsDepth which users can turn on, but unfortunately I don't see us diving into JS files to try to do inference by default.

akdor1154 commented 7 years ago

What I am hinting towards is some way of me writing in my library's package.json that Typescript should indeed dive into its JS files by default, or at least as far as it has to to type the externally visible interface. Strawman: "types": "[jsdoc]". This would (hopefully) void the performance concerns of a full node_modules JS-inference-spree.

yawaramin commented 6 years ago

How about:

Write your library in JS as usual. In the same project, write declaration files for your JS library modules. Basically, pretend that you're writing typings for a third-party module ( https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html ). If you want to strongly type the JS library internals, write JSDoc annotations for them as you're doing now.

Now, when a consumer using TS uses your module, it automatically gets the TS type definitions. And you also get them while you're developing the library since they're already in the same project.

Klaster1 commented 6 years ago

I found this issue while looking to solve a similar issue, but in a different scenario.

There are two applications, one is open source and is not designed to behave like a dependency, so no d.ts files are generated because that's out of project scope, but the code is extensively covered with JSDoc-style TS types. Then there's another app, which builds upon the open source one by providing extra features. The TS files from the base app do get correct types, but at the moment these are almost nonexistent, so I want to leverage existing typings from JSDoc during JS to TS migration.

The "maxNodeModuleJsDepth: 2" in the second app does the trick, but more explicit setting like what @akdor1154 proposed would IMHO be better.

jkrems commented 5 years ago

I found this while writing up recommendations for adding typescript as a lint step to our libraries and applications. This issue makes typescript a lot less viable than flow as a type linting solution (e.g. a compilation-less way of adding type checking to projects that don't require a compilation step). Having to guess (?) the correct value of maxNodeModuleJsDepth on the consumer side (it seems to be affected by potential import depth / internal project structure of the library being required..?) is a pretty hacky workaround.

Maybe the JS library could use a package.json field to mark itself as "typed"? I'm really enjoying the better jump to definition when there isn't a type definition file so I don't want to start suggesting to people that they add one.

DavidWells commented 5 years ago

Hello there! Came here via https://twitter.com/orta/status/1176216539713953793

I recently added Typescript support to my project that is using JSdocs.

My setup is like this:

  1. My code is vanilla JS with JSdoc blocks for the types
  2. During my build process, I use tsd-jsdoc to convert the JSdocs from the source to a types.d.ts type file. https://github.com/DavidWells/analytics/blob/master/packages/analytics-core/package.json#L31
  3. In my package.json, I set the path to where typings lives https://github.com/DavidWells/analytics/blob/master/packages/analytics-core/package.json#L50

It would be super awesome if TS automatically inferred the types from the JSdocs in my built source code. This would streamline things for me 😃

Anywho, thanks for all the great work on TS & the tooling around it. The autocompletion the package has is great!

jonathanKingston commented 1 year ago

I found this thread just before creating the feature request; here's the template filled out:

Suggestion

As a plain JavaScript project using JSDoc types; I'd like the ability to include node_modules that I publish without having to produce .d.ts files.

Having the ability to filter node_modules that match maxNodeModuleJsDepth would prevent modules that are out of my control / don't provide types etc.

maxNodeModuleJsDepth solves some of the issues but it becomes cumbersome very quickly when their types are invalid or implicit any etc.

🔍 Search Terms

node_modules

✅ Viability Checklist

My suggestion meets these guidelines:

⭐ Suggestion

In tsconfig.json have:

{
  "compilerOptions": {
    "target": "es2021",
    "moduleResolution": "node",
    "alwaysStrict": true,
    "allowJs": true,
    "checkJs": true,
    "noEmit": true,
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true,
    "declaration": true,
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "maxNodeModuleJsDepth": 1
  },
  "include": [
      "**/*.js"
  ],
  "includeNodeModuleJs": [
      "@thing/other",
      "lib",
      "@thing/lib"
  ],
  "exclude": [
  ]
}

This would check only the "@thing/other", "lib", "@thing/lib" that were 1 module deep without needing to exclude any other libs at that depth.

📃 Motivating Example

We're using a zero build step repo that has typed library code and wish to import it into duckduckgo-autofill

💻 Use Cases

SnareChops commented 3 months ago

My vote would be to put the burden of configuration on the package publisher, not the consumer.

The existing package.json field types usually points to a .d.ts file in a typescript repo, and this allows consuming projects to find the types you have included in your package. If a package author has decided to use JSDoc to fully type their package then they should be able to specify that by pointing the types property to their .js entrypoint. The TypeScript compiler would then know that this package's types should be parsed from the JSDoc comments, instead of a .d.ts file.