Why Svelte broke up with TypeScript??!

Why Svelte broke up with TypeScript??!

Svelte and SvelteKit's experimentation with JSDoc as an alternative to TypeScript.

I want to preface this article by saying and [spoiler alert], while I might agree with Svelte's decision to migrate to JSDoc and see the benefits it offers over TypeScript, I'm still a HUGE fan of TypeScript and all the awesome features it brings to the table.

Introduction

Remember when Svelte's creator Rich Harris migrated from TypeScript to JavaScript for the Svelte 4 codebase? TypeScript has been widely adopted for it's benefits such as type checking, intelliSense and inline documentation, so why the switch? The reason why Svelte decided to leave TypeScript (which maybe the right call) is because the Svelte team believed that while types are great, TypeScript as a language can be "a bit of a pain" quoting Rich Harris. The big problem with TypeScript is all the extra hassle and headaches it brings. Like, imagine you're making a library in TypeScript and then using it in another project. You can't just tweak the code and have it work right away. Nope, you have to rebuild everything and that just makes everything a lot more complicated. To circumvent these issues, the Svelte team decided to use JavaScript with JSDoc annotations for type safety. This way gives you all the good stuff of type safety without running into the downsides that usually come with TypeScript.

Just to be clear, we're talking about building Svelte, the library itself. It's got nothing to do with how you'll actually use Svelte as a developer. You can still use Svelte with TypeScript, no problem.

TypeScript: The GOAT

According to the 'State of JavaScript: The annual developer survey of the JavaScript ecosystem' showing TypeScript usage rise. The question that was asked in the survey — How do you divide your time between writing JavaScript and TypeScript code?

js_ts_balance.png (2080×972)

TypeScript's type-checking is what makes it so popular with developers. But it did come with it's downsides that is it requires an extra transpilation step, which is a time-sucker. Um, the scope of this article is not going into depth about the what and how of TypeScript, I would like to focus more on what JSDoc is and how it works similarly to TypeScript. But hey, let's save that conversation for another time.

What is JSDoc

Svelte still supports TypeScript, it hasn't abandoned type safety, just the type declarations have been moved from .ts files to .js files with JSDoc annotations. JSDoc is a syntax for leaving comments in your code that tells your IDE what each step or each function does. The JSDoc syntax serves multiple purposes, including annotating values with types, specifying parameters and return types for functions, documenting and providing usage information for functions, typing errors, etc. Similar to TypeScript, these could then be used as guides by code editors for programmers while they build, consume, and maintain the codebase.

JSDoc vs/ TypeScript

TypeScript and JSDoc both solve the problem of writing and maintaining plain JavaScript code. Nevertheless, they used different approaches, which both had pros and cons.

What JSDoc has over Typescript

Using JSDoc with JavaScript offers several advantages.

  1. Flexibility and Compatibility — With JSDoc, you can include JavaScript comments in any JavaScript codebase, regardless of language version, and you're not tied to a compiler like TypeScript.

  2. Code Annotation — Type checking isn't the only thing JSDoc can do. This could help increase code maintainability and understanding by adding more documentation, explaining how functions work.

  3. No Compilation Step — This is one of the most motivating reasons to switch to JSDoc coming from TypeScript. The TypeScript code needs to be compiled into JavaScript for the browser to understand it, but JSDoc doesn't require any compilation since they're just 'comments' with JavaScript. As a result, it simplifies and speeds up development workflow compared to having to use Typescript build pipelines every time.

The downsides of using JSDoc

Okay, so here's the deal: JSDoc has its perks, no doubt. But you know what? TypeScript is still gaining popularity and there's a reason for it. TypeScript has some serious advantages over JSDoc:

  1. Stronger Static Typing — TypeScript provides a strong model for types, so these errors are caught at compile time. This is unlike JSDoc, where the typing ends in the code itself.

  2. Type Inference — TypeScript can infer the type from its value. By doing this, you reduce explicit type annotations and make the codebase less verbose.

  3. Transpilation — With its polyfill feature, TypeScript can adopt the latest and future JavaScript features. The code is effectively transpiled into understandable versions for browsers that don't support the features yet.

How to use JSDoc

You don't need to install anything - JSDoc works straight out of the box in all modern editors. Adding JSDoc to a .js file is a breeze; you just start a comment with an extra '*' and you're good to go. I'll just drop in a snippet from VS Code because Hashnode doesn't pick up JSDoc comments in its Code Block feature.

// Regular JavaScript Single Comment
/* Regular JavaScript Multi-line Comment */

/**
 * JSDoc comment containing two asterisks
 */
/** The name of the language JSDoc is written for */
const language = "JavaScript"

Adding types to values

/**
 * This is a JSDoc demonstration
 * @type {string}
 */
const userName = "Pranav"

Now, it denotes that the userName variable should be of type string.

Adding types to objects and arrays

/**
 * @type {Array<string>}
 */
const groceries = ['Apples', 'Almonds', 'Ketchup'

/**
 * @type {number[]}
 */
const randomNumbers = [9, 8, 26, 13]

Typing functions

/**
 * Represents a Person
 * @param {string} name - The name of the person.
 * @param {number} currentYear - The current year that is going on.
 * @param {number} birthYear - The person's birth year.
 * @returns {number} The person's age 
 */
function Person (name, currentYear, birthYear) {
  return currentYear - birthYear
}

The @param keyword, represents the value the defined function would accept. You can also add some description of what the parameter is with a hyphen (-). So, you know when you're writing a big function and you want to know what it's supposed to give back? That's where the @returns keyword comes in handy. It saves you from having to dig through all the code, even those early returns, to figure out what the function is supposed to do.

If the function is a constructor for a class, you can indicate this by adding a @constructor tag —

/**
 * Represents a Person
 * @constructor
 * @param {string} name - The name of the person.
 * @param {number} age - The person's age.
 */
function Person (name, age) {
}

To create an object type use the @typedef directive —

/**
 * @typedef {Object} User - A schema for User
 * @property {number} id
 * @property {string} username
 * @property {string} email
 * @property {Array<number>} postLikes
 * @property {string[]} friends
 */
/** @type {User} */
const user1 = {
  id: 9,
  username: "Pranav",
  email: "pranav@some.com",
  postLikes: [212, 432, 421]
  friends: ['D', 'A']
}
/** @type {User} */
const user2 = {
  id: 8,
  username: "Arnav",
  email: "arnav@some.com",
  postLikes: [112, 422, 420]
  friends: ['P', 'A']
}

Typing a complete class

/**
 * Person Class
 * @class
 * @classdec A Person class to get the name of the person and set it using getter and setter methods.
 */
class Person {
  /**
   * Initializing a Person object.
   * @param {string} name - The name of the person.
   */
    constructor(name) {
        this.name = name;
    }
  /**
   * Getter function for the name of the person.
   * @returns {string} The name of the person.
   */
    get personName() {
        return this.name;
    }

  /**
   * Setter function for setting the name of the person.
   * @returns {string} The name of the person.
   */
    set personName(x) {
        this.name = x;
    }
}

let person1 = new Person('Pranav');
console.log(person1.name); // Pranav

// Changing the value of name property
person1.personName = 'Arnav';
console.log(person1.name); // Arnav

Above is a simple class with two methods to get a name string and set it. The @class keyword is used to show that a function is needed to be called with the new keyword. @classdec is used to describe the class. When typing classes, it is important to go further by adding types and descriptions to —

  1. The constructor

  2. All methods and variables created within the class

Improving General Code Documentation

Apart from just adding basic types to your code, JSDoc does a lot to improve your code's readability and ease of understanding. Here are a few ways it helps:

Adding Code Authors

The author of an element could be added using the @author directive with the name and email of the author —

/**  
 * Possible title for this article  
 * @type {string}  
 * @author Pranav [pranav@some.com] 
 */ 
const articleTitle = "JSDoc: An alternative to TypeScript"

Example Usage

You may also include some code snippets that demonstrate how to use a particular block of code. You can use this for those tricky bits of code —

/** 
 * Multiplies two numbers a * b
 * @example <caption>How to use the multiplyNumbers function</caption>
 * // returns 6 
 * multiplyNumbers(2, 3)
 * @example
 * // returns 20
 * multiplyNumbers(4, 5)
 * // Typing the function
 * @param {number} a - The first number
 * @param {number} b - The second number
 * @returns {Number} Returns the product of the two numbers
 */
const multiplyNumbers = function(a, b){
    return a * b;
}

We use the @example directive to achieve this. This can also be captioned with the caption tag.

Versioning

You could also specify the version of an element using the @version directive —

/**
 * @version 7.1.0
 * @type {number}
 */
const version = 7.1.0

Creating Modules

To create a module, simply use the @module tag at the beginning of your file. This designates the file as a module, and it will be organized accordingly in a separate section on the generated documentation website.

// jsdoc.js
/** @module indexDoc */
// The rest of the code would go here

Converting JSDoc Files

One of the great things about JSDoc is that you can convert JSDoc files into either a documentation website or even into TypeScript. This way, you get all the perks of using TypeScript, like catching errors before runtime and seamlessly integrating with TypeScript projects.

Generating .d.ts files from JSDoc

In TypeScript, .d.ts files are declaration files that hold types accessible to all .ts files in a project. You can create these files from JSDoc code using these steps —

Install jsdoc

npm install -g jsdoc

Install tsd-jsdoc in your repository

npm install tsd-jsdoc

Generate .d.ts files

For a single file

jsdoc -t node_modules/tsd-jsdoc/dist -r our/jsdoc/file/path.js

For multiple files

jsdoc -t node_modules/tsd-jsdoc/dist -r file1.js file2.js file3.js ...

For entire directory

jsdoc -t node_modules/tsd-jsdoc/dist -r src

It combines all types from file(s) into a single file in out/types.d.ts.

Conclusion

In the end, will JSDoc replace TypeScript for type checking? Absolutely not! And if that's the question you're asking, you've missed the point. TypeScript is awesome for building apps, and it keeps getting even better. TypeScript has also added support for many of the JSDoc declarations. But when it comes to making libraries, using JSDoc annotations in regular ol' JavaScript seems like the way to go. Most developers build applications, not libraries, that's why TypeScript will continue to be the primary choice for type checking... until JavaScript incorporates native type checking, that is 💀.