46

Can I define all custom types in a separate file (e.g. types.jsdoc), so that they can be reused throughout the application? What's the right way to do it?

/**
 * 2d coordinates.
 * @typedef {Object} Coordinates
 * @property {Number} x - Coordinate x.
 * @property {Number} y - Coordinate y.
 */
Edward Ruchevits
  • 6,411
  • 12
  • 51
  • 86
  • Yes you can. You may have to add `@global` to the definitions, or experiment with the different ways to namespace stuff in JSDoc (confusing, IMHO, and for my own purposes which only is WebStorm inlineinfo/help and HTML API documentation works well). – Mörre Oct 01 '17 at 21:44
  • I had the same question using Visual Studio Code. I suggested this [answer](https://stackoverflow.com/a/55767692/1244884) which you *may* find useful. – customcommander Apr 19 '19 at 20:51

6 Answers6

20

You can define types in a module (eg. typedefs.js). The module contains your JSDoc typedefs and can simply export an unused property.

// typedefs.js
/**
 * @typedef foo
 * @property {string} bar
 */

// etc.

exports.unused = {};
// or export {};

To use it, import the module where you need to reference these typedefs:

const typedefs = require("./typedefs");
/** @type {typedefs.foo} */
const fb = { bar: "hello" };

You may wish to annotate typedefs.js as a @module or @namespace. Because I'm using "tsd-jsdoc" to generate a types.d.ts file, and due to the way TypeScript now interprets modules vs. namespaces, I've annotated my typedefs.js file as a @namespace and documented each typedef as a member of that namespace:

/**
 * @namespace typedefs
 */

/**
 * @typedef foo
 * @property {string} bar
 * @memberof typedefs
 */

Hope that helps.

Darren
  • 1,846
  • 15
  • 22
  • 4
    If you are using ES6 import/export, you can use `export {};` to export nothing while still marking the file as a module. – Phil Kang Aug 14 '20 at 11:40
  • With \@module, the types would work in VSCode, but the links in generated docs were broken. With \@namespace, both VSCode and generated docs work. Been looking for this solution for a long time. Thanks! – W. Murphy Aug 03 '22 at 21:17
12

This is a TypeScript-flavored JSDoc specific answer, but I'm having success using a triple-slash directive to "import" all the types from another file. This has the advantage of not actually adding an unused import which can upset linters and bundlers.

I'm putting my shared types in one file called typedefs.js like this:

// typedefs.js
/**
 * @typedef {Object} Foo
 * @property {string} bar
 */

/**
 * @typedef {Object} Baz
 * @property {number} buzz
 */

and then using /// <reference path="typedefs.js" /> in the other files to access the shared types like this:

// randomThing.js
/// <reference path="typedefs.js" />

/**
 * Turn a Foo into a Baz
 *
 * @param {Foo} a
 * @return {Baz}
export function (a) {
  return { buzz: a.bar.length };
}

The tricky thing though is that now typedefs.js is just being referenced in a comment, bundlers like rollup miss it completely. So I'm combining it with my old consts.js that exports a few constants and is imported in at least one place. That way the typedefs are still included in the rollup output.

I hope someone else finds this helpful.

p.s. rollup will completely exclude a pure JSDoc typedefs.js file _even if you have import './typedefs.js' because of tree-shaking! Gotta run rollup with --no-treeshake to keep those comments in the rollup output.

William Hilton
  • 2,613
  • 1
  • 15
  • 14
6

In vscode, the import('./path/to/types.js').def tag works perfectly fine.

For e.g.
types.js

/**
 * @typedef {Object} connection
 * @property {String} id
 * @property {Number} pingRetries
 * @property {(data:Object) => void} sendJSON
 */
exports.unused = {};

And someFile.js

/**
 * @param {import('./types').connection} param
 */
const someFunc = (param) => {}

Also, note that the exports.unused = {} is necessary in the types.js file, otherwise the auto-import of import('./types') would not work and you may have to type it by yourself.

Shivam Goyal
  • 71
  • 1
  • 3
4

I just tried with VSCode and it works only if the separate file is opened in the editor. If not, external typedefs are typed as any

  • 2
    Thanks for the comment. I was excited to see that my types were picked up and I thought it was from the workspace, but it stopped when I closed the file as you'd suggested. – zavr Jul 18 '18 at 20:23
  • Yeah, this is what I just finally discovered... Did you ever find a solution for this? I've been trying things for hours now with no luck. :/ – Dustin Perolio Aug 15 '20 at 22:21
  • Using v1.68.0 even though the file is not opened it is resolving the types. Yay! – BruceJo Jun 09 '22 at 22:57
1

I usually do something similar in my projects, the difference being I use the extension .js to name the file. Webstorm works perfectly and is able to check types and auto-complete just fine. It won't recognize the .jsdoc extension (I just checked), so stick to .js even if the file doesn't contain any code statement.

Lucio Paiva
  • 19,015
  • 11
  • 82
  • 104
1

I've had success with simply creating my types in a typedefs.js file and referencing using the ts/vscode import(path/to/file).Foo tag. JSDoc does not support this syntax out of the box, so I suggest also using jsdoc-tsimport-plugin in order to parse your docs.

Eg: typedef.js:


/**
 * @typedef {Object} Foo
 * @property {string} id
 */

/**
 * @typedef {Object} Bar
 * @property {string[]} things
 */

// having to export an empty object here is annoying, 
// but required for vscode to pass on your types. 
export {};

coolFunction.js

/**
 * This function is super dope
 * @param {import('../typedef').Foo[]} foo - a foo
 * @return {import('../typedef').Bar[]} bar - an array of bars
 */

 export function (foo) {
    // do cool things
    return bar;
 }

I'm also using tsd-jsdoc to create a types.d.ts file, and this implementation is successfully creating the types. I had trouble with declaring modules and namespaces with the types file— just creating standalone typedefs for said models worked best for me.