147

Let's say I have a file named "File1.js". In this file, I export an object of objects and I give each object a typedef, like so.

/**
 * My typedef for each object.
 * @typedef {Object} MyObject1
 * @property {String} username Your username
 * @property {String} realname Your real name.
 * @property {boolean} isUnique Are you unique as a person?
 */
module.exports = {
  /**
   * Person One!
   * @type {MyObject1}
   */
  myperson: {
    username: 'TheDragonSlayer',
    realname: 'George',
    isUnique: true
  },
  /**
   * Person Two!
   * @type {MyObject1}
   */
  myperson2: {
    username: 'BobMagee',
    realname: 'Bob',
    isUnique: false
  }
}

Now, in a file named "File2.js", I reference this object in a constructor and set it to a new MyObject1.

const persons = require('./File1.js');

class File2 {
  constructor(options = {}) {
    /**
     * The person for this file.
     * @type {MyObject1}
     */
    this.person = options.person ? persons[options.person] : persons.myperson2;
  }
}

module.exports = File2;

I use Visual Studio Code to develop, so by pressing Ctrl+Space I get IntelliSense. Within file one and while I'm making the person objects, IntelliSense tells me that username is a string, realname is a string, and isUnique is a boolean. But, when I go into file2 and reference the newly made person via this.person, when typing this.person.username it does not come up with the expected result of "Username: String".

Is it possible to use the typedef MyObject1 in File2 in vanilla Node.js, or am I out of luck?

Edit: With some more information, I was able to find answers with @export and @import for TypeScript, as well as a tag of sorts that I tried as well. All of which to no avail. I also tried marking File1.js as a @module, and doing module:mymodule~MyMethod, but every time I did that it'd just mark this.person as a NodeModule instead of the method itself.

5
  • Did you mean @typedef instead of @typdef in File1?
    – Peter G
    Commented Apr 14, 2018 at 22:42
  • @PeterG Yes, sorry about that! Commented Apr 14, 2018 at 22:47
  • 1
    It may just be a question of how intelligent Intellisense is rather than a JSDoc thing. Using WebStorm IDE, I found this scenario works as expected but I'm often finding limits to JSDoc support - for example it's not working as expected when the @typedef is in a dependency project. Commented Apr 19, 2018 at 8:55
  • There's a comment to this effect on the one answer below, but import("some-module") is supported by Typescript but is not official JSDoc.
    – Coderer
    Commented Jan 21, 2021 at 11:22
  • Also using import "./types.js" and @typedef in types.js file works for me.
    – aderchox
    Commented Sep 15, 2022 at 17:08

8 Answers 8

228

Import the declared type in your file File2.js using the function import.

const persons = require('./File1.js');

/**
 * @typedef {import('./File1.js').MyObject1} MyObject1
 */

class File2 {
...

It works for me.

enter image description here enter image description here

3
  • 22
    This is TypeScript-specific syntax. Vote for github.com/jsdoc/jsdoc/issues/1645.
    – glen-84
    Commented May 28, 2020 at 10:42
  • 15
    Any way to not repeatedly type import('./File1.js')? (if I need e.g also file1.MyObject2 and file1.MyObjectN). – Doing @typedef {import('./File1.js')} file1 and @typedef {file1.MyObject2} MyObject2 MyObject2 is only aliased and looses definition details, getting marked as /*unresolved*/ any. Commented Aug 29, 2022 at 16:00
  • 2
    This is not working for me in VS Code. Curiously, it works when I do NOT import the file at all. Seems VS code scans typedefs and imports them automatically. That's not a great solution on their side. Commented Aug 24, 2023 at 11:48
45

Another thing I discoverd working is to export and empty model and then use it as a reference to the types:

types.js:

/** 
* @typedef MyType 
* @prop {string} name  
* @prop {string} description 
*/

export const Types = {}

Then in your other files you can import that and have the types from that dummy object:

File1.js

import * as Types from "./types.js"

/** @type {Types.MyType} */
const myVar = { name : 'Roy', description : 'abc123'};

pros:

  • alot "cleaner" - less code to write
  • only have to import once

cons:

  • a bit hackey
  • not idiomatic
  • you might get the error:

This import is never used as a value and must use 'import type' because 'importsNotUsedAsValues' is set to 'error'

If you have that flag on.

To get around that warning you only need disable it with a ts-ignore:

// @ts-ignore  
import * as Types from "./types.js";
6
  • 9
    This is so powerful with vanilla js. it makes typescript almost seem redundant
    – lys
    Commented Feb 3, 2023 at 8:32
  • 1
    It seems like uou do not need to export Types. Simply importing ./types.js such as : import "./types.js"; will work Commented Feb 15, 2023 at 10:54
  • 1
    I would suggest imitating the naming convention of "compiled" typescript types files such as name.d.js Commented Feb 15, 2023 at 10:56
  • 1
    Actually i use typescript files like name.d.ts in my js projects. They are ignored by js
    – Chen Peleg
    Commented Feb 15, 2023 at 21:28
  • This is a brilliant solution, ticks a lot of boxes for me. You can make this a little cleaner by using an inline import and a typedef instead of the import * line: /** @typedef {import('./types.js').MyType} MyType */ Commented May 2, 2024 at 14:33
30

Typescript 5.5 introduced @import syntax. So you can use ESM like syntax in JSdoc like you'ld do In Typescript.

From your example, it becomes:

const persons = require('./File1.js');
/** @import { MyObject1 } from './File1.js' */

class File2 {
  constructor(options = {}) {
    /**
     * The person for this file.
     * @type {MyObject1}
     */
    this.person = options.person ? persons[options.person] : persons.myperson2;
  }
}

module.exports = File2;
2
  • thanks! this is only solution for me that works for both VS Code and jsdoc cli
    – han4wluc
    Commented Sep 3, 2024 at 1:04
  • To import types in JSDoc correctly, use the import() syntax within a //@typedef declaration, as //@import is not a valid directive in JSDoc. For example, to import the StateObservable type from the redux-observable package, you would write: /** @typedef {import('redux-observable').StateObservable} StateObservable */. This approach clearly indicates the source of the type, enhancing code readability and maintainability. Ensure your development environment supports import() in JSDoc to avoid configuration issues.
    – Gelloiss
    Commented Sep 6, 2024 at 9:06
9

There's another way.

create the types.js

/**
 * @typedef  {object} LINUX
 * @property {string} filePath      The target the shortcut points to.
 * @property {string} [outputPath]  Path where shortcut will be placed. Defaults to user's desktop.
 * @property {string} [comment]     Metadata file "comment" property. Description of what the shortcut would open.
 */

/**
 * @typedef  {object} OSX
 * @property {string} filePath      The target the shortcut points to.
 * @property {string} [outputPath]  Path where shortcut will be placed. Defaults to user's desktop.
 * @property {string} [name]        Name of the shortcut file.
 */

just import them on your file.js

import './types.js'
/** @type {LINUX} */
const Linux = {}
1
  • This one is much cleaner.
    – priyabagus
    Commented Sep 21, 2024 at 9:01
5

For those that do not need to import it, but still want the IntelliSense to work, there is another solution, posted here: https://stackoverflow.com/a/55767692/9427691

In short: For VC-Code javascript IntelliSence you can create a jsconfig.json file in the project root folder, that informs which files to include. like so:

{
  "include": [
    "src/**/*.js"
  ]
}
3

Here is the cleanest way I've found. I've verified that it works in VSCode for auto-complete/intellisense and shows the information on hover. TypeScript engines should also be able to infer all type info from this.

First off I create a file in the root called api-type-definitions.js. I'll flatten any nested objects/methods/functions so that each is it's own named type definition. Then they can reference each other as needed. Then I create an actual JavaScript variable and assign it the type and export the variable.

/**
 * OPTIONAL: console.error is called by default if verbose: true.
 *
 * @callback {Function} CUSTOMLOGGER
 * @param    {string}   message       The human readable warning/error message
 * @param    {object}   [error]       Sometimes an error or options object is passed
 * @return   {void}
 */

/**
 * @typedef  {object} WINDOWS
 * @property {string} filePath               The target the shortcut points to.
 * @property {string} [outputPath]           Path where shortcut will be placed. Defaults to user's desktop.
 * @property {string} [windowMode="normal"]  How the window should be displayed by default. Valid inputs: 'normal', 'maximized', 'minimized'. Defaults to 'normal'.
 */

/**
 * @typedef  {object} LINUX
 * @property {string} filePath      The target the shortcut points to.
 * @property {string} [outputPath]  Path where shortcut will be placed. Defaults to user's desktop.
 * @property {string} [comment]     Metadata file "comment" property. Description of what the shortcut would open.
 */

/**
 * @typedef  {object} OSX
 * @property {string} filePath      The target the shortcut points to.
 * @property {string} [outputPath]  Path where shortcut will be placed. Defaults to user's desktop.
 * @property {string} [name]        Name of the shortcut file.
 */

/**
 * @typedef  {object}       OPTIONS
 * @property {CUSTOMLOGGER} [customLogger]  Called (if verbose: true) with helpful warning/error messages from internal validators.
 * @property {WINDOWS}      [windows]       Windows shortcut settings.
 * @property {LINUX}        [linux]         Linux shortcut settings.
 * @property {OSX}          [osx]           OSX shortcut settings.
 */

/**
 * @type {LINUX}
 */
let LINUX;

/**
 * @type {OPTIONS}
 */
let OPTIONS;

module.exports = {
  LINUX,
  OPTIONS
};

Note that I only export the types I'll be reusing. The rest of the definitions are just flattened for easier writing/reading.

Then in any file where I want to use the type, I import the variable and reference it in the JSDoc block as the type.

const { OPTIONS } = require('./api-type-definitions.js');

/**
 * Creates OS based shortcuts for files, folders, urls, and applications.
 *
 * @param  {OPTIONS} options  Options object for each OS, and global options
 * @return {boolean}          True = success, false = failed to create the icon or set its permissions (Linux).
 */
function createDesktopShortcut (options) {
  // the library code
}

module.exports = createDesktopShortcut;

Example repos:

I would also highly recommend eslint-plugin-jsdoc, here are my rules if you want a jumping off point:

2
  • is there a way to exclude these arbitrary files in a production build without transpiling? Since they just add to the overall size without adding value outside of an IDE
    – wattry
    Commented Jan 9, 2024 at 3:25
  • 1
    If you are doing minification and uglification (all bundlers, like Vite, do this for you), the comments will automatically be removed, and these type definition variables will be uglified to a few characters. they should not have any measurable impact on performance. Commented Jan 11, 2024 at 4:08
3

TypeScript supports @import since the TypeScript 5.5 Beta.

/** @import { SomeType } from "some-module" */

/**
 * @param {SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}

And for default imports:

/** @import * as someModule from "some-module" */

/**
 * @param {someModule.SomeType} myValue
 */
function doSomething(myValue) {
    // ...
}

https://devblogs.microsoft.com/typescript/announcing-typescript-5-5-beta/#type-imports-in-jsdoc

2

Another solution to explore is to add a typescript declaration file @types/myTypes.d.ts... IDEs know how to deal with these and since they are .d.ts, they are automatically "imported" into each file.

The only downside is that these must be pure typescript, so you can't have anything like arrays in them.

It's also handy to add other @types to a project like @types/chrome for chrome extensions (e.g. yarn add @types/chrome)

You may need to configure your IDE to use them as a library. Here's where to do it in WebStorm: enter image description here

1
  • not jsdoc though. that's typescript speaking
    – oligofren
    Commented Oct 25, 2023 at 9:59

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.