Skip to content

Commit ce5d055

Browse files
committed
chore: initial blog post version
1 parent b0ba751 commit ce5d055

File tree

1 file changed

+275
-0
lines changed

1 file changed

+275
-0
lines changed

README.md

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
---
2+
title: Generating TypeScript Definition Files from JavaScript
3+
published: false
4+
description: -
5+
tags: javascript, typescript, jsdoc, buildless
6+
---
7+
8+
First of let me say that I have been putting of this blog post for quite a while.
9+
I am a little afraid that I am not going to do TypeScript justice.
10+
The reason for that is that we are going to use it heavily - but in a rather indirect way.
11+
12+
See - we are a big fan of a [buildless]() development setup. We have [a post]() or [two]() about it :grimmacing:
13+
It is [our believe](https://open-wc.org/about/rationales.html) that it is the best way to bring developers (you) and the platform (browser) back on the same table.
14+
15+
Knowing this makes it hard to root for TypeScript as it is a [Transpiler Language]() - in other words it requires a build step.
16+
17+
So how come we are still fans?
18+
Let's dive into and see what Types can give you.
19+
20+
#### We will start by writing some tests in TypeScript:
21+
22+
```js
23+
// helpers.test.ts
24+
import { square } from '../helpers';
25+
26+
expect(square(2)).to.equal(4);
27+
expect(square('two')).to.equal(4);
28+
```
29+
30+
Our plan is to accept a number and a string and return the power of it with the default of power of 2 (e.g. square it).
31+
32+
Let's implement it with TypeScript:
33+
34+
```ts
35+
// helpers.ts
36+
export function square(number: number) {
37+
return number * number;
38+
}
39+
```
40+
41+
So yeah I know what you have been thinking - a string as an argument?
42+
While implementing we found out that it was a bad idea.
43+
And thanks to the power of types we can just go back to our code/tests and tada we immeditelly see in vscode that `square('two')` is not working.
44+
45+
![01-ts-square-two](https://github.com/daKmoR/generate-typescript-definition-files-from-javascript/blob/master/images/01-ts-square-two.png)
46+
47+
And we will of course get the same if we try to run `tsc`.
48+
49+
```bash
50+
npm i -D typescript
51+
```
52+
53+
```bash
54+
$ npx tsc
55+
helpers.tests.ts:8:19 - error TS2345: Argument of type '"two"' is not assignable to parameter of type 'number'.
56+
57+
8 expect(square('two')).to.equal(4);
58+
~~~~~
59+
60+
Found 1 error.
61+
```
62+
63+
### Let's make the same in JavaScript
64+
65+
For the tests only the import change to `*.js`.
66+
67+
```js
68+
// helpers.test.js
69+
import { square } from '../helpers.js';
70+
71+
expect(square(2)).to.equal(4);
72+
expect(square('two')).to.equal(4);
73+
```
74+
75+
For the code we removed the type
76+
77+
```js
78+
// helpers.js
79+
export function square(number) {
80+
return number * number;
81+
}
82+
```
83+
84+
And our if we go back to the test now we do not see that `square('two')` is wrong :(.
85+
86+
![02-js-square-two](https://github.com/daKmoR/generate-typescript-definition-files-from-javascript/blob/master/images/02-js-square-two.png)
87+
88+
So that is the power of types. But we can make it work for JavaScript as well :hugs:
89+
90+
Let's add a types via JsDoc
91+
92+
```js
93+
/**
94+
* @param {number} number
95+
*/
96+
export function square(number) {
97+
return number * number;
98+
}
99+
```
100+
101+
and configure TypeScript to check for JavaScript as well by adding a `tsconfig.json`.
102+
103+
```json
104+
{
105+
"compilerOptions": {
106+
"target": "esnext",
107+
"module": "esnext",
108+
"moduleResolution": "node",
109+
"lib": ["es2017", "dom"],
110+
"allowJs": true,
111+
"checkJs": true,
112+
"noEmit": true,
113+
"strict": false,
114+
"noImplicitThis": true,
115+
"alwaysStrict": true,
116+
"types": ["mocha"],
117+
"esModuleInterop": true
118+
},
119+
"include": ["test", "src"]
120+
}
121+
```
122+
123+
Doing this allows as to get exaclty the same behavior in VSCode as with TypeScript.
124+
125+
![03-js-square-two-typed](https://github.com/daKmoR/generate-typescript-definition-files-from-javascript/blob/master/images/03-js-square-two-typed.png)
126+
127+
We even get the same behavior when running `tsc`.
128+
129+
```bash
130+
$ npx tsc
131+
test/helpers.tests.js:8:19 - error TS2345: Argument of type '"two"' is not assignable to parameter of type 'number'.
132+
133+
8 expect(square('two')).to.equal(4);
134+
~~~~~
135+
136+
Found 1 error.
137+
```
138+
139+
#### Enhancing our code
140+
141+
Let's assume we want to also add an offset (best I could come up with :see_no_evil:)
142+
e.g.
143+
144+
```js
145+
expect(square(2, 10)).to.equal(14);
146+
expect(square(2, 'ten')).to.equal(14);
147+
```
148+
149+
First up `TypeScript`:
150+
151+
```
152+
export function square(number: number, offset = 0) {
153+
return number * number + offset;
154+
}
155+
```
156+
157+
I assume you are wondering why we do not have a type here?
158+
It is because a default value let's TypeScript set the type based on this default value.
159+
160+
And now the same for `JavaScript`:
161+
162+
```js
163+
/**
164+
* @param {number} number
165+
*/
166+
export function square(number, offset = 0) {
167+
return number * number + offset;
168+
}
169+
```
170+
171+
In both cases it will give
172+
173+
```bash
174+
test/helpers.tests.js:13:22 - error TS2345: Argument of type '"ten"' is not assignable to parameter of type 'number'.
175+
176+
13 expect(square(2, 'ten')).to.equal(14);
177+
~~~~~
178+
```
179+
180+
Also in both cases the only thing we needed to add was `offset = 0` as it contains the type information already.
181+
182+
If you wanna know more about how to use JSDoc for types I can recommend you these blog posts.
183+
184+
- [Type-Safe Web Components with JSDoc](https://dev.to/dakmor/type-safe-web-components-with-jsdoc-4icf)
185+
- [Type Safe JavaScript with JSDoc](https://medium.com/@trukrs/type-safe-javascript-with-jsdoc-7a2a63209b76)
186+
187+
### Publishing a library
188+
189+
If someone is to use your code you will need to publish it. Usually that happens on npm.
190+
You will also want to provide those types to your users.
191+
That means you will need to have `*.d.ts` files in the package you are publishing.
192+
As those are the only files that `TypeScript` respects by default in the `node_modules` folder.
193+
194+
##### What does it means for TypeScript?
195+
196+
When we publish we will run `tsc` with these settings
197+
198+
```json
199+
"noEmit": false,
200+
"declaration": true,
201+
```
202+
203+
that way TypeScript will generate `*.js` and `*.d.ts` files.
204+
It can do so fully automatic as it knows all the types - as it is TypeScript.
205+
206+
The output will be
207+
208+
```js
209+
// helpers.d.ts
210+
export declare function square(number: number, offset?: number): number;
211+
212+
// helpers.js
213+
export function square(number, offset = 0) {
214+
return number * number + offset;
215+
}
216+
```
217+
218+
e.g. the output of the js file is exactly the same we wrote in our js version.
219+
220+
#### What does it means for JavaScript?
221+
222+
Sadly as of now `tsc` does not support generating `*.d.ts` files from JSDoc annotated files.
223+
But it probably will be in the future. The original [issue](https://github.com/microsoft/TypeScript/issues/7546) is from 2016 but recently it has been said was planned for `3.6` (but it didn't make it into beta) so it seems it on the board for for `3.7`. However don't take my word for it as here is a working [Pull Request](https://github.com/microsoft/TypeScript/pull/32372).
224+
225+
And it is working so great that we are using it even in production for [open-wc](https://github.com/open-wc/open-wc/blob/master/package.json#L7).
226+
227+
> WARNING
228+
> This is an unsupported version => if something does not work no one is going to fix it.
229+
> Therefore if your usecase is not supported you will need to wait for the offical release of TypeScript to support it.
230+
231+
So you have been warned if you still think it's a good idea to test it you feel free to do so.
232+
We published a forked version [typescript-temporary-fork-for-jsdoc](https://www.npmjs.com/package/typescript-temporary-fork-for-jsdoc) which is just a copy of what the above Pull Request is providing. (again to be clear - we did not change anything it is a temporary fork which is good enough for our use case).
233+
234+
## Generate TypeScript Definition Files for JSDoc annotated JavaScript
235+
236+
So now that we have all the information. Let's just make it work.
237+
238+
1. Write your code in js and apply JSDoc where needed
239+
2. Use the forked TypeScript `npm i -D typescript-temporary-fork-for-jsdoc`
240+
3. Have a `tsconfig.json` with at least
241+
242+
```json
243+
"allowJs": true,
244+
"checkJs": true,
245+
```
246+
247+
4. Do "type linting" via `tsc`
248+
5. Have `tsconfig.build.json` with at least
249+
250+
```json
251+
"declaration": true,
252+
"allowJs": true,
253+
"checkJs": true,
254+
"emitDeclarationOnly": true,
255+
```
256+
257+
6. Generate Types via `tsc -p tsconfig.build.types.json`
258+
7. Publish your `*.js` AND `*.d.ts` files
259+
260+
Ideally doing the type linting happens in a `pre-commit` hook and generating the `*.d.ts` files happens in the ci for publishing.
261+
We have exactly this setup at [open-wc](https://github.com/open-wc/open-wc) and it served as well so far.
262+
263+
Congratulations you now have a type safety without a build step :tada:
264+
265+
#### To sum it all up - why are we fans of TypeScript even though it requires a build step?
266+
267+
It comes down two 2 things
268+
269+
- Typings can be immensely useful (type safety, auto complete, documentation, ...) for you and/or your users
270+
- TypeScript is very flexible and supports types for "just" JavaScript as well
271+
272+
Follow us on [Twitter](https://twitter.com/openwc), or follow me on my personal [Twitter](https://twitter.com/dakmor).
273+
Make sure to check out our other tools and recommendations at [open-wc.org](https://open-wc.org).
274+
275+
Thanks to [Benny](https://dev.to/bennypowers) and [Lars](https://github.com/LarsDenBakker) for feedback and helping turn my scribbles to a followable story.

0 commit comments

Comments
 (0)