Type hints on pure .js files
Manuel Artero Anguita 🟨
Posted on April 27, 2022
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
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: ...
}))
}
Later we might have something like:
function doSomethingWithUserTickets(username, tickets) {...}
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
- 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"]
}
- 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';
};
}
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: ...
}))
}
+ /**
+ * @param {string} username
+ * @param {TicketingSystem.Ticket[]} tickets
+ */
function doSomethingWithUserTickets(username, tickets) { ... }
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
}
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) { ... }
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 }) => { ... }
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 💚.
Posted on April 27, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.