TypeScript has support for type-checking plain JavaScript files, which is very useful if you have an existing JS codebase and you want to test the waters and gradually add types.
There are some limitations in what you can do in JSDoc, but a lot of them can be worked-around by using type-definition files .d.ts
(for example in a types/
directory). These files don't generate any JavaScript code, they are just there to provide extra type definitions to the compiler.
One thing you can't do in those .d.ts
files though, is use enums. You could define them of course, but you won't get the runtime representation since the files don't generate JS code.
The solution I found requires a bit more boilerplate and is more error-prone than the pure TypeScript version, but it seems to work.
Instead of defining an enum in your type definition file, you define both a union type and an interface:
// types/models.d.ts
declare namespace Models {
type ProductTag = "popular" | "featured" | "sale";
interface ProductTagEnum {
Popular: "popular";
Featured: "featured";
Sale: "sale";
}
interface Product {
id: string;
name: string;
tags: Array<ProductTag>;
}
}
Then you create a runtime representation of your "enum" using the interface. You can use this representation elsewhere in your code.
// app/models/product.js
// @ts-check
/** @type {Models.ProductTagEnum} */
const ProductTag = {
Popular: "popular",
Featured: "featured",
Sale: "sale"
};
/**
* @param {Models.Product} product
* @returns {boolean}
*/
function isPromoted(product) {
return (
product.tags.indexOf(ProductTag.Featured) >= 0 &&
product.tags.indexOf(ProductTag.Sale) >= 0
);
}
- There is more boilerplate because you basically have to define the enum 3 times: the union
type
, theinterface
, and the runtimeconst
. - It is more error-prone because the compiler won't check that the union
type
and theinterface
are "in sync" (but you'll probably get an error when you try to use yourconst
). - You don't have to define the runtime
const
, you could just use a string directly (ex:product.tags.indexOf("featured")
), but it makes it harder to track down where you are using your enum values (ex: can't use your IDE's "find usages" feature, need to search for the string which could show up in comments etc.)
// tsconfig.json
{
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"jsx": "react",
"module": "commonjs",
"moduleResolution": "node",
"noEmit": true,
"strict": true,
"target": "es6"
},
"include": [
"app/**/*",
"types/**/*"
]
}
Very late to the party but wanted to leave this comment for future folks who land here.
You can perform
as const
in JSDoc, it's just a little ugly. Here's how you would do it.That will result in the same behavior as