Yeah, you absolutely can. And you don't need TypeScript declaration files either.
TypeScript will happily check your vanilla JS code if you have the checkJs
flag set in either CLI config or your tsconfig.json
file. allowJs
actually has nothing to do with it, that just enforces whether you can import vanilla JS files into TS/TSX files (it doesn't care if you import JS into JS). JSDoc annotation is actually a great way to mix vanilla JS and TS code in a mixed codebase, for example if you're in the process of migrating code from JS to TS.
TypeScript (even without JSDoc annotations) will give you tons of useful "implicit" type checking without writing any extra syntax, just by detecting how you declare variables and assuming that their type shouldn't change (among other inferences):
// Implicit TypeScript typing
let myBooleanValue = false;
myBooleanValue = 'true'; // Error: Type 'string' is not assignable to type 'boolean'.ts(2322)
If you want even more control however, TypeScript is fully compatible with JSDoc type annotations. Make sure you read the documentation and use the proper comment syntax (e.g. /** ... */
not // ...
). There's just about nothing you can do with TypeScript syntax that you can't do with JSDoc, it's just a bit wordier:
// Explicit JSDoc typing
/**
* @type {string}
*/
let myVar;
myVar = 7; // Error: Type 'number' is not assignable to type 'string'.ts(2322)
You can get even more advanced with it, declaring things like function parameters, object structure and nested fields, return values, and more:
/**
* @param {object} myParam
* @param {number} myParam.myNumber
* @returns {boolean} // Error: A function whose declared type is neither 'void' nor 'any' must return a value.ts(2355) (because nothing is currently returned from myFunction)
*/
function myFunction(myParam) {
let myNumberCopy = myParam.myNumber;
let myMissingPropertyCopy = myParam.myMissingProperty; // Error: Property 'myMissingProperty' does not exist on type '{ myNumber: number; }'.ts(2339)
}
You can even do crazy stuff like import types from other files or packages and use it in your JSDoc annotations:
/**
* @type {import("React").FC}
*/
const MyComponent = () => null;
Here is the tsconfig.json
file I used for the above examples, with nothing except basic typescript
NPM package installed:
{
"compilerOptions": {
"module": "es6",
"noEmit": true,
"checkJs": true,
"moduleResolution": "node"
},
"include": ["src/**/*"]
}
Visual proof that this all works great in VSCode:

With this setup a good way to "test" whether you have any type errors in your entire codebase is to run npx tsc --noEmit
, which will run TypeScript checking (including the JSDoc annotations) for all your files, according to your tsconfig.json
file.