Skip to content
This repository has been archived by the owner on Nov 16, 2023. It is now read-only.

Using typescript babel to transition from flow #1

Open
rgbkrk opened this issue Feb 22, 2018 · 5 comments
Open

Using typescript babel to transition from flow #1

rgbkrk opened this issue Feb 22, 2018 · 5 comments

Comments

@rgbkrk
Copy link

rgbkrk commented Feb 22, 2018

What would be the recommended practice for switching from flow to typescript?

We (nteract) have a large code base (as a lerna monorepo) written with flow and this seems like a good way for us to transition individual packages to typescript, since everything is currently babel backed in our code base.

@DanielRosenwasser
Copy link
Member

TLDR:

  • go one project at a time; then within a given project...
  • add a tsconfig.json with strict on
  • replace the Flow Babel plugin with the TypeScript plugin
  • rename from .js to .ts
  • as you go through each project, keep the strictest settings on and declare your monorepo dependencies in a .d.ts file

This is a good question; I think that one of the challenges you may face is that type annotations are not considered valid in .js files, so in that regard, you may need to migrate Flow files to use the .ts extension.

Additionally, when trying to import from node_modules, TypeScript will complain under noImplicitAny if you don't have a .d.ts file for a respective .js file. To get around this (while using the strictest settings), each project can temporarily add a declare module "foo/*" for a given inter-project dependency.

So for example, if you have the following layout once you've compiled everything:

Root
+- a
|  + a/lib/index.js
|
+ b
| + b/lib/index.js
|
+ c
  + c/lib/index.js
  + c/lib/something/else.js

If b depends on a and c, then in b/src/, you might define an external-types.d.ts with the following ambient module declarations:

// Note: this file cannot have any imports or exports; it needs to be globally scoped

declare module "a";
declare module "c";
declare module "c/lib/*";

One more thing is that for a given compilation, Babel can't have both TypeScript and Flow plugins loaded at the same time, so you'll need to remove the Flow plugin for a given project's compilation.

@DanielRosenwasser DanielRosenwasser changed the title Using typescript babel to transition to flow Using typescript babel to transition from flow Feb 22, 2018
@rgbkrk
Copy link
Author

rgbkrk commented Feb 22, 2018

This helps give me an idea of how I should start this off, thank you so much for taking the time to write it. I'm guessing the hardest part of this is assessing how long this refactor will take and any other costs. Right now I'm still a bit scared of the investment, it helps me plan for doing it after a couple more releases of our apps+packages though.

@richard-lopes
Copy link

Thanks for answering this.
I am curious though if there could be any ways to keep using .js and .jsx extensions for Javascript code annotated with TS types?

@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Mar 28, 2018

@richard-lopes Nope, not at this time. We've tried to avoid confusing users with syntax that won't actually work at runtime.

@rbiggs
Copy link

rbiggs commented Sep 2, 2018

To play the devil's advocate, as an alternative to using Flow or switching to TypeScript, you can get TypeScript type safety for JavaScript.

TypeScript Types for JavaScript

You can keep your files as Javascript and use TypeScript for checking types if you're using VSCode: Type Checking JavaScript. Doing this provides many of the famous benefits of TypeScript, such as intellisense: code completion, Go to Definition, Peek Definition, Find All References, Rename Symbol (across files), Change All Occurences, hover type reveal, etc. You can read more about TypeScript intellisense through JSDoc on the VSCode site. VSCode does not support every feature of JSDoc. You can check out their documentation of the features they do support for intellisense.

Setup

Open user settings and add:

"javascript.implicitProjectConfig.checkJs": true

This tells VSCode to use TypeScript to type check the JavaScript files. Now in this the trick is to use JSDoc comments to document your types. When the TypeScript language service sees JSDoc comments, it uses those to understand the JavaScript's type system.

You can define a type using @type:

/**
 * @type {string} name
 */
function announce(name) {
  alert(`Hello, ${name}!`)
}

You can define a custom type using @typedef:

/**
 * @typedef {Object<string, any>} Props
 * @type {Props} props
 */
function Title(props) {
  return (
    <h1>{props.message}</h1>
  )
}

You can also define generics using the @template tag:

/**
 * @param {T} t
 * @template T
 */
const Bar = function (t) { return};

var bar = new Bar("hello"); // bar is a Bar<string>

You can also import type definitions from other files:

/** 
 * @typedef {import('../vnode').VNode} VNode
 * @param {VNode} vnode
 */
function createNodeFromVnode(vnode) {
  // Do stuff here...
}

You can also do type casting, but it is more verbose. Say you have an element of type Node but you want to add an attribute to it. setAttribute is on Element, not Node, so you would get a type error. You can use a JSDoc comment to cast the Node to Element like this. Notice how we use an inline type definition and we tell the language service to apply to the node by surrounding it in braces.

// btn will be type Node:
const btn = document.createElement('button')
// Make btn disabled.
// Since it's type Node, we need to cast it to type Element:
/** @type {Element} */(btn).setAttribute('disabled')

Nice thing about JSDoc comments, including the verbose type casting, when you minify all of it goes away, leaving you with just JavaScript.

Type Checking at Build Time

You can add TypeScript type checking for your JavaScript as part of your build process with an NPM script:

"checkjs": "tsc --allowJs --checkJs --noEmit --target ES6 lib/*.js"

D.TS Files

If you look at the default .d.ts files that TypeScript provides, you'll see that they make use of JSDoc comments for better intellisense. Microsoft recommends using JSDoc for enhancing the intellisense that TypeScript produces. VSCode has built in support for JSDoc comments. They also recommend JSDoc for documenting JavaScript code in Visual Studio for intellisense.

JSDoc Comments are Standard JavaScipt

Unlike TypeScript, which needs to be compiled, or Flow, which needs to be stripped out at build time, JSDoc comments are standard JavaScript comments. This means that the type information they provide stays in the source code and can also be in the development version of the code before minification. Minification with Uglify will automatically strip out the JSDoc comments, including any extra markup for type casting. Although proponents of TypeScript and Flow are fond of saying they are just JavaScript, you can run them in the browser as is. JavaScript with JSDoc types can.

Expando Properties

Because TypeScript is a static type checker that only checks during code time and build time, it can't handle expando properties. Instead it will flag these as not existing on the object. When using JSDoc for types, because TypeScript is running under the hood, you'll run into the same problem. Although using expando properties is a normal thing in JavaScript, to use them with TypeScript you need to escape them with braces and quotes.

The following code is valid JavaScript:

const btn = document.createElement('button');
// Add custom property
btn.mounted = true;
document.body.appendChild(btn)

With TypeScript type checking, mounted would be flagged as not existing on type Node. Escaping mounted eleviates the problem:

const btn = document.createElement('button');
// Add custom property
btn['mounted'] = true;
document.body.appendChild(btn)

JSDoc vs TypeScript

Which is better? If you want the very best support for types, go with TypeScript. If you just want to be able to write JavaScript and use Babel stage 0 and stage 1 now, but you want enhanced intellisense and type safety, use JSDoc.

If your team consists of geeking JavaScript nerds who are hesitant about introducting types, go with JSDoc. If your team have previously worked with C++, C# or Java, they're going to love TypeScript.

TypeScript features may not always match Babel features. Take for example how TypeScript implements private members in a class with the private declaration, whereas the ECMAScript proposal for class fields has a different approach. When you use JSDoc for types, there is no disparity in what you're writing and what it will be in JavaScript because Babel is based on actual ECMAScript proposals.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants