Supercharge Your JavaScript: Static Type Checking in VSCode
Background
Whether willing or not, we have to use JavaScript rather than Typescript in some scenarios, e.g., legacy codebases and config files, so we do not have type-checking and auto-suggestions for those codes.
As a fan of Typescript, code without type lets me lack security, so I’m using tricks to enable static type checking of JS code in the VSCode.
Write JSDoc
A common way to add type hints to JS code is to write JSDoc in the comments. Here is a simple example:
/**
* @param {Object} params The param of the func is an Object
* @param {string} params.required A required field
* @param {boolean} [params.optional] An optional field
*
* @returns {number} Return value
*/
export function func(params) {
return 'no error';
}
So that you can get a hint when hovering over the function name and provide accurate auto-complete when typing.
However, simply using JSDoc does not introduce any constraints on the code. You will not be notified when calling functions with the wrong parameters.
Enable static type-checking
The type hint without constraints does not satisfy my needs and can not make me feel safe. I expected the VSCode to tell me all the type inconsistencies when writing JS code, like Typescript code.
Fortunately, Typescript has the settings for checking the JS code:
- To check all js files, set the
allowJs
and thecheckJs
option totrue
in thetsconfig.json
- To enable type-checking for specific files in the legacy codebase. Add the
// @ts-check
comment to the first line of the files we want to check.
Then the code will be checked and we get warnings:
Just warnings! Nothing will be broken!
You can enable this check anywhere, progressively, with confidence. VSCode produces those warnings and only breaks the existing build process once you add a type-checking step.
Combined with Typescript
It works well now, but there are still some inconveniences:
- Write JSDoc will be a hard job, especially for complex objects.
- Sharing and referencing the same JSDoc type across files will be troublesome.
We can step one further, introducing Typescript types into the code.
Firstly, create a .ts
file. I usually name it js-file-name.interface.ts
. Describe your types with Typescript in this file.
/**
* Type definition in the `.ts` file
*/
export interface IFuncParams {
required: string;
optional?: boolean;
}t
Then import them in the js files.
All valid types can be used with this approach, not only the interfaces but also the types from npm packages. It’s easy to maintain and share across the code.
Real-world examples
React components
babel.config.js
Config function:
/**
* @type {import('@babel/core').ConfigFunction}
*/
module.exports = api => {
return {
presets: [],
};
};
Or if you use config object:
/**
* @type {import('@babel/core').TransformOptions}
*/
module.exports = {
};