Confident JS series: Part 2 — Types, JSDocs, and declaration files

Using TypeScript without Typescript

Daniel Kuroski
Homeday
Published in
10 min readJun 9, 2021

--

This is part 2 of a series:

TypeScript without TypeScript

I have a love-hate relationship with TypeScript, I enjoy static type languages and I love functional paradigm despite I don’t understand a lot of things yet 😂.

TypeScript does a great job by being as flexible as JavaScript and you also can make it as stricter as needed, not something as hardcore as Elm, but it can get really close.

The type system surprised me when I learned about infer, conditional types and when I worked with libraries like fp-ts and io-ts (more about that in future parts of this series 😉).

But I always have a hard time when I need to configure it or introduce TS in a legacy system.

You truly see why TypeScript “is the way it is” when you begin some migration, I’m not here to give a step-by-step nor teach how to do that (maybe in a future article).

If you are interested, I highly recommend Mike North courses in Frontend Masters in case you want to learn techniques on how to migrate properly a system without affecting negatively your team and company.

I’m here to show how you can softly introduce a few of the benefits of the language without adding anything new, seriously, you can just open VSCode and start using it.

If you are in a similar situation as mine, maybe going slow with soft changes is the way to go.

You can experiment without compromising time, avoid worrying about configurations and any other shenanigan, then you can see how your team react to that and evaluate if a migration is worth a shot.

TS in JS files

The key here is JSDocs:

JSDoc 3 is an API documentation generator for JavaScript, similar to Javadoc or phpDocumentor. You add documentation comments directly to your source code, right alongside the code itself. The JSDoc tool will scan your source code and generate an HTML documentation website for you.

Let’s use the calculateTotalPrice function from the previous article to see it in action, here is a slightly different version of it:

/**
* Calculates a product total price
* @param {number} price
* @param {number} vat
* @returns {number}
*/
function calculateTotalPrice(price, vat) {
return price + vat
}

This is just plain JS with JSDocs, you must follow the comment conventions to make it work, so the doc block (everything inside of the comments /** ) in the calculateTotalPrice function will result in documentation and auto-completion.

JSDoc function autocompletion in VSCode

Any editor that has support to JSDoc will interpret that, we can even generate documentation pages and document other things like variables using /** @type {type-of-the-variable} */

JSDoc variable auto-completion in VSCode

You can even create custom types

/**
* A Product representation
* @typedef {Object} Product
* @property {number} price
* @property {number} vat
*/
/**
* Calculates a product total price
* @param {Product} product
* @returns {number}
*/
function calculateTotalPrice(product) {
return product.price + product.vat
}

And with that, you have auto-completion:

Product custom type auto-completion

And documentation:

calculateTotalPrice function call documentation

JSDocs have a wide range of special properties you can use to document your application:

Type checking in a JavaScript file

JSDoc is just a way to document your code. To improve your confidence while coding, we can rely on type checkings.

You can enable type checking in JavaScript files by adding // @ts-check to the top of a file.

Please note that your editor must support TS. In their website we can find a list, at the time I’m writing this article they have listed Visual Studio, Visual Studio Code, Nova, Atom, Sublime Text, Emacs, Vim, WebStorm, and Eclipse.

Let’s go back to our previous example to see the result of that:

/**
* A Product representation
* @typedef {Object} Product
* @property {number} price
* @property {number} vat
*/
/**
* Calculates a product total price
* @param {Product} product
* @returns {number}
*/
function calculateTotalPrice(product) {
return product.price + product.vat
}

If we invoke this function with wrong arguments, the code editor won’t complain at us:

calculateTotalPrice({ price: 10, vat: “1.2” })

We are sending the wrong parameter since vat is supposed to be an actual Number but no warning is raised, let’s add // @ts-check to the top of the file then:

compile-time error when invoking calculateTotalPrice function with the wrong parameter

We have errors 🎉, that’s great! Let’s fix the data type

Correct “calculateTotalPrice” call

That’s awesome, we have now:

  • Documentation
  • Auto-completion
  • Compile-time errors (static checking)

You can enable type checking per file by using // @ts-check or in your entire project by using one of the following approaches:

Remember that nothing is enforced, you can still ignore the errors and run your application normally, fixing them is up to you.

Going further by using declaration files

TypeScript has a lot of extra features that help us building safer applications.

We can use Union Types, intersection types, helper types, declarations, custom types, type inference, Generics, and much more.

We can use a few of those features just by relying on JSDocs, check a few examples below:

  • Using generics
/**
* @template T
* @param {T} x - A generic parameter that flows through to the return type
* @return {T}
*/
function id(x) {
return x;
}
const a = id("string");
const b = id(123);
const c = id({});
  • Union types
/**
* @type {number | null}
* With strictNullChecks: true -- number | null
* With strictNullChecks: false -- number
*/
var unionNullable;

For more information, please check their guide:

Just using JSDocs and enabling // @ts-check is enough to help out creating safer applications, but we can go a step further.

TypeScript has the concept of declaration files, you probably already saw some of those weird .d.ts files.

In the TS documentation, declaration files are defined as:

.d.ts files are declaration files that contain only type information. These files don’t produce .js outputs; they are only used for type-checking. We’ll learn more about how to write our own declaration files later.

I like to think that:

Declaration files are special files that contain “blueprints” of your types

TypeScript already infers types, and we can make use of the compiler to generate those declaration files based on the type inference for us.

// sum.ts
function sum(a: number, b: number): number {
return a + b
}
// by running the compiler and asking to generate the declaration file, typescript will generate something like// sum.d.ts
export function sum(a: number, b: number): number;

More on that here:

The nice thing about those declaration files is that you are free to create your files, enhance the type definitions you already have and even type other JS libraries.

There is even a famous repository that groups type definitions from the community:

To see how this works, let’s use our previous example. I’ve created below a types.d.tsfile in my root folder and I provided all the type annotations.

// types.d.ts
export type Product = {
price: number;
vat: number;
}
export function calculateTotalPrice(product: Product): number;

And then we can use the type like that

// index.js
// @ts-check
/** @type {import("./types").calculateTotalPrice} */
function calculateTotalPrice(product) {
return product.price + product.vat
}
calculateTotalPrice({ price: 10, vat: 1.2 })
Using type declaration files

If you are using some bundler like Webpack, don’t worry, declaration files won’t take part in the end build.

You also don’t have to worry about the naming. There are some naming conventions that you can try to follow, more on that here:

Be aware that if you are using TS directly, maybe you don’t need to create declaration files for your application code.

Its very likely that you will end up just using them to do things like:

  • Add global definitions
  • Enhance/override definitions from third party libraries

Two real-world examples

Example 1 – Encoding and Decoding

Below I re-wrote the example from the previous part by using just the content I’ve shown in this article:

Example application mixing JSDocs with declaration files

The end result is pretty much what we already saw

Decoder documentation
Encoder function documentation
TS inferring export statement
Auto-completion
Type checking
Type checking 2

Example 2 – Object keys to camel case and snake case

Let’s do the same with the utility functions from the previous example.

I’m using humps library to handle the “casing” transformations, they have a declaration file in the DefinitelyTyped repository, but I want to make use of my own custom types since I want better auto-completion.

Let’s customize a bit, bellow you can find one way on doing it:

Overriding humps type implementation

Which gives us a nice result

Using the functions
Auto-completion

We can also use a different approach and enhance humps library, just the way is being done in the DefinitelyTyped repo, check below for a simplified approach:

And then we get the same result without the need on adding the /** @type {} */ definition (because we are using // @ts-check )

Enhancing third-party libraries

Using with Vue

At Homeday we make heavy use of Vue.js in our projects, this section contains a few notes on how you can make use of JSDocs and declaration files to type-check Vue specific cases

Vue 2 with JSDocs

Thanks to Vetur we have all the benefits even in the templates!

Unfortunately, we have a limited environment for typing in Vue 2, some times you won’t be able to properly type, especially in cases like the store or mixins.

Composition API helps a lot, since is way easier to add type definition to the values inside of the setup function and for the “hooks”.

But most of those problems are solved with Vue 3 and the newest versions of the libraries in the ecosystem, I won’t cover that here since I still didn’t have much experience with the newer versions.

Conclusion

JSDocs and type checkings are awesome!

By using the examples from this article, you will be able to softly introduce some benefits of JSDocs and TypeScript in your ongoing application without any trouble.

This might be a good first approach if you are planning on migrating your app to TypeScript.

But please, it’s important to remember to evaluate with caution before going on any kind of migration.

Making sure your application has enough maturity (tests) to face this kind of migration is crucial for its success.

I want to highlight the article and the documentation that I learned about this theme:

Thank you for reading this article. Please stay tuned to the next parts.

Do you like what you see?

Give us a clap, leave a comment, or share anywhere. We appreciate your feedback.

Also, check out other articles on our blog. We deal with many interesting things:

Check out our openings at our dev team!

Sorry for the long post, here is a potato dressed as a cat.

--

--