DEV Community

Cover image for Type hints on pure .js files
Manuel Artero Anguita 🟨
Manuel Artero Anguita 🟨

Posted on • Updated on

Type hints on pure .js files

Back in 2015 Typescript was starting. Believe me or not there was a feeling of rejection by the dev community. I guess because it was the new guy in the office.

If you ask me, Typescript is awesome. Like a fancy suit.

But sometimes you're looking for a casual look; sport jacket and jeans.

What if you're ok with some type hints here and there while skipping the compilation step. Keeping just .js files.
Well, it's possible actually (at least in VSCode).


Let's say we have this minimal structure

  ├── package.json
  └── src
      │── index.js
      └── api.js
Enter fullscreen mode Exit fullscreen mode

At api.js we're fetching some third party, mapping values and building our own models. For example:

function getTickets(username) {
  return fetch("http://example.com/api/endpoint")
    .then((response) => ({ 
      id: ...,
      name: ..., 
      status: ...
    }))
}
Enter fullscreen mode Exit fullscreen mode

Later we might have something like:

function doSomethingWithUserTickets(username, tickets) {...}
Enter fullscreen mode Exit fullscreen mode

So far so good, let's improve intellisense.


First, we need to add two files at the root of our project, tsconfig.json and types.d.ts

  ├── package.json
+ ├── tsconfig.json
+ ├── types.d.ts
  └── src
      │── index.js
      └── api.js
Enter fullscreen mode Exit fullscreen mode
  • At tsconfig.json we're going to say "hi ts, do take a look to these .js files but do not compile or something.":
{
    "compileOnSave": false,
    "compilerOptions": {
        "noEmit": true,
        "allowJs": true,
        "checkJs": true,
        "target": "es6",
        "resolveJsonModule": true,
        "moduleResolution": "node"
    },
    "include": ["./src"]
}
Enter fullscreen mode Exit fullscreen mode
  • At types.d.ts we'll declare any type we might need in our domain:
declare namespace TicketingSystem {
    type Ticket = {
        id: string;
        name: string; 
        status: 'OPEN' | 'IN_PROGRESS' | 'RESOLVED' | 'BLOCKED';
    };
}
Enter fullscreen mode Exit fullscreen mode

Usually I name the namespace the same as the project (name at package.json); but this is personal.

Now, use those types as JSDocs, VSCode will honor JSDoc.

+ /**
+  * @param {string} username
+  * @return {Promise<TicketingSystem.Ticket[]>}
+  */
 function getTickets(username) {
   return fetch("http://example.com/api/endpoint")
     .then((response) => ({ 
       id: ...,
       name: ..., 
       status: ...
     }))
 }
Enter fullscreen mode Exit fullscreen mode
+ /**
+  * @param {string} username
+  * @param {TicketingSystem.Ticket[]} tickets
+  */
 function doSomethingWithUserTickets(username, tickets) { ... }
Enter fullscreen mode Exit fullscreen mode

External types

As soon as we get further 'hello world', we install npm packages... and sooner or later we'll end up referencing a type from the third party.

Good news are that we're also covered: suggestion is to create a type alias at types.d.ts

 declare namespace TicketingSystem {
     type Ticket = {
         id: string;
         name: string; 
         status: 'OPEN' | 'IN_PROGRESS' | 'RESOLVED' | 'BLOCKED';
     };
+
+   /* third party alias */
+   type Context = import("third-party-package").Context // whatever
+   type Storage = import("another-package").Storage // whatever
 }
Enter fullscreen mode Exit fullscreen mode

Doing this, we're able to use them as any other type around:

/** 
 * @param {TicketingSystem.Ticket} ticket
 * @param {TicketingSystem.Context} context
 * @param {TicketingSystem.Storage} storage
 */
function saveTicket(ticket, context, storage) { ... }
Enter fullscreen mode Exit fullscreen mode

Final tip

I use object parameters like a lot. this is how you type those:

/** 
 * @param {object} param0
 * @param {TicketingSystem.Ticket} param0.ticket
 * @param {TicketingSystem.Context} param0.context
 * @param {TicketingSystem.Storage} param0.storage
 */
function saveTicket({ ticket, context, storage }) => { ... }
Enter fullscreen mode Exit fullscreen mode

I do encourage full typescript setup for medium and big size projects. But this idea of "just some interfaces here and there" do the trick for small projects, POC, bash utilities and scripting.

--

Banner image: People illustrations by Storyset

thanks for reading 💚.

Latest comments (0)