Making Typescript packages JavaScript friendly
February 06, 2018 -I don’t know about you, but when I first started using Typescript it was an absolute game changer for me. Even for simple projects, where I’d be writing less than 100 lines of JavaScript, I’d always opt-in to using Typescript instead.
Now, this wasn’t because I’m a massive fan of Microsoft, or even because of static typing, it was solely because Sublime Text would give me hints as I typed, but besides, why would it matter what I used? The compiled JavaScript would be usable by everyone… Right?
If you’ve used Typescript, you’ve definitely had to deal with importing a JavaScript package before, it goes something like this:
import * as express from "express";
Isn’t that just so ugly?
Okay, so we’ve imported a JavaScript module with Typescript, let’s create a Typescript module to require with JavaScript.
// Greeting.ts
export class Greeting {
private greeting;
constructor(name: string, greeting: string) {
this.greeting = greeting;
}
greet() {
console.log(`${name} says ${this.greeting}`);
}
}
Let’s try importing that with Typescript
import { Greeting } from "Greeting";
let hello = new Greeting("Me", "Hi!");
hello.greet();
// Me says Hi!
And with JavaScript
const Greeting = require("Greeting").Greeting;
let hello = new Greeting("Me", "Boo!");
hello.greet();
// Me says Boo!
That Javascript is a little bit tedious, but it’s understandable since we’re choosing one of the exports of the Greeting module anyway. Let’s try and make that a little bit better.
// Greeting.ts
export default class Greeting {
...
}
Now we shouldn’t have to choose a module when we import with Typescript
import Greeting from "Greeting";
let hello = new Greeting("Me", "Hey!");
hello.greet();
// Me says Hey!
Awesome! Since we’re not choosing an export like last time, requiring with Javascript should work perfectly.
const Greeting = require("Greeting");
let hello = new Greeting("Me", "Hello!");
hello.greet();
// undefined
Wow, that’s a little bit annoying. What’s going on? Well when Typescript tries to compile a default export into Javascript, it can’t just make the whole module the export. Instead, it does it like this
...
module.exports.default = Greeting;
...
So now the part that you’ve been waiting for. The obvious answer to a timeless question that no-one is going to ask.
How do I write Typescript modules that work exactly the same as Javascript modules would?
Well, you’d write them like you would in Javascript of course!
There is a little bit of a catch though, you can’t write it like this.
export default class Greeting {
...
}
module.exports = Greeting;
Why? Because the Javascript would look something like this
module.exports.default = Greeting;
module.exports = Greeting;
and since Typescript looks for that .default
when importing, you’re gonna have a whole bunch of problems. Infact, the solution likes in the index.ts
of your module.
import Greeting from "./Greeting";
module.exports = Greeting;
export default Greeting;
VoilĂ ! We’ve created a module that’s intuitive to import with both Typescript and JavaScript.
There are a few caveats though, most notably that if the main export class has any methods that share the same name as other exports there’s going to be issues. This won’t be a problem 50% because of naming conventions (classes having capital letters or whatever), but if you’re exporting a similarly named function or variable you’re going to run into issues.
Just on a final note, kinda like my first article this whole making Typescript packages intuitive to use with JavaScript is really a non-issue, since no one guesses how to use a package without first reading the documentation. Oh well.