Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate types from Flow to TypeScript (?) #3458

Open
gnprice opened this issue Apr 15, 2019 · 5 comments
Open

Migrate types from Flow to TypeScript (?) #3458

gnprice opened this issue Apr 15, 2019 · 5 comments
Labels

Comments

@gnprice
Copy link
Member

gnprice commented Apr 15, 2019

We've figured for a long time that we'll eventually want to move to TypeScript; it's much better supported for the open-source community than Flow is, and seems to be increasingly the choice that people outside Facebook use and that outside tools best support.

For the Zulip webapp, there's long been a hope to migrate it from plain JS to TypeScript. When that work gets going in earnest, it'll open a quite valuable opportunity to share the maintenance of the types we have in src/api/ to describe the Zulip server's API. To do that effectively, we'll want to be using TypeScript ourselves.

Fortunately, the tools for doing this have gotten better in recent years, and it should be a quite reasonable amount of work. The basic plan is:

  • Adjust the Babel config so that it looks for *.ts files, and compiles them as TypeScript instead of Flow. (This only became possible with Babel 7.)
  • Start using tsc as a type-checker, as well as flow. (Add it to tools/test, and make sure our recommended VS Code config supports it well.)
  • Convert files to TypeScript, one at a time.
    • I expect neither type-checker will be super effective when the codebase is half and half... but at least they can work on their respective sides, and the code can all run correctly.
    • We'll want to not spend too much time in this intermediate state, though.
  • When that's done, optionally switch away from Babel to tsc entirely.

For converting a given file, or swath of files:

  • See this post.
  • There are some high-volume, highly-mechanical things like converting {| ... |} to { ... }; and some low-volume things that are more manual.
@gnprice gnprice changed the title Migrate types from Flow to TypeScript Migrate types from Flow to TypeScript (?) Sep 18, 2019
@gnprice
Copy link
Member Author

gnprice commented Sep 18, 2019

I still expect this will probably eventually be the best choice for us -- it's probably what the Zulip web frontend will end up on, and it'd certainly be good to be on the same toolchain there and here.

But there are real downsides, applicable equally to the mobile app and the web frontend: most of all, TypeScript doesn't even try to be sound.

I knew that already at a one-line level of detail, but recently realized it's much worse than I'd thought. Details in chat. In particular, if you routinely use the "user-defined type guards" feature in the intended way... then TS behaves less like "a type-checker" than "a pretty OK linter". 😞

Maybe if you just don't use that feature, things are better? Like C++.

@gnprice
Copy link
Member Author

gnprice commented Sep 19, 2019

Maybe if you just don't use that feature, things are better? Like C++.

On the linked chat thread, @andersk devised a small helper which can be employed to safely use this feature. The helper itself remains unchecked, but there's only one of it; and user-defined type guards based on it contain type-checked proofs of their soundness. And I think the bulk of legitimate uses of this feature can be done cleanly atop that helper.

That's not so bad, then. It makes the analogy with C++ rather closer than I thought: if this issue is representative, then it's possible to use TypeScript (and even some of its sharper-edged features) as a mostly-safe language in the same way as C++, by having someone on the team take the time to become an expert in (a) the hidden pitfalls and (b) the tricks for systematically avoiding them.

Still disappointing. One reason it still leaves me concerned in general is that in the case of C++, the tricks for avoiding the pitfalls are generally well known in the community and seen as best practices, and I think were often specifically intended by the language designers when the pitfalls were introduced -- whereas in this case, Anders's solution feels to me like it's not in the spirit of TypeScript, not something the language designers would intend people to do. Which suggests they intend people to use this feature blatantly unsafely, and gave no warning of that in the docs... which is not promising when it comes to all the other choices they've made that we haven't yet studied in detail.

(Also, "as sound as C++" is not exactly high praise for a type system designed from scratch this century.)

@rk-for-zulip
Copy link
Contributor

rk-for-zulip commented Oct 16, 2019

For future reference: https://github.com/joarwilk/flowgen is a tool to automatically extract Flow interface files from TypeScript, which may be useful if we find ourselves in a world with both Flow and TypeScript files.

... which, as it happens is the world we're in anyway: several of our dependencies are TypeScript rather than Flow — likely soon including the Zulip server front-end: see (our) issue #3538, or its dual zulip/zulip#13253.

@gnprice
Copy link
Member Author

gnprice commented Jan 25, 2020

I'm encouraged to see this update about Flow development -- though I'm nearly a year late in spotting it, so it actually predates my filing this issue:
https://medium.com/flow-type/what-the-flow-team-has-been-up-to-54239c62004f

The simple and honest explanation is that we were too heads-down on addressing Facebook-internal challenges, and felt too resource-constrained to do much else.

This is exactly what I'd inferred from what's publicly visible. It's encouraging to see them acknowledge it frankly -- and the details in the post sound credible that they've made solid progress on those internal challenges, and may be largely through them by now (a year later). Perhaps Flow will have a future beyond Facebook after all.

I'm also encouraged by the progress in the latter half of 2019 on a big cluster of the most annoying problems in Flow's type system, in object spreads:
https://medium.com/flow-type/coming-soon-changes-to-object-spreads-73204aef84e1
https://medium.com/flow-type/spreads-common-errors-fixes-9701012e9d58

chrisbobbe pushed a commit to chrisbobbe/zulip-mobile that referenced this issue Apr 29, 2020

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
Do not use `remotedev-serialize` directly. In the next commit, we
fix a security hole by wrapping this dependency and recommending the
use of the wrapper.

In an upcoming commit, we will set up a system of "replacer" and
"reviver" transforms so that we can store non-serializable objects,
such as ZulipVersion instances, in Redux, and have a serializable
format be persisted to disk.

This same system will be used for non-serializable objects besides
ZulipVersion instances; see zulip#3949 and zulip#3950, which will be much
easier after this series.

For the Flow types for these modules:

Thankfully, `immutable`, being much more popular, has Flow types out
of the box. Some assembly is required for a libdef for
`remotedev-serialize`, but it's important to get one working [1].

1. Check to see if someone has already submitted a libdef to
   FlowTyped, with `flow-typed install remotedev-serialize`. There
   isn't; we get the output, '!! No [email protected] libdefs
   found in flow-typed for the explicitly requested libdefs. !!
   Consider using `flow-typed create-stub remotedev-serialize` to
   generate an empty libdef that you can fill in.'.

2. As that output suggests, run

   `flow-typed create-stub remotedev-serialize`

   . This creates `flow-typed/npm/remotedev-serialize_vx.x.x.js` and
   fills it with a template based on the directory structure in
   `node_modules/remotedev-serialize`. Move this to
   `flow-typed/remotedev-serialize_vx.x.x.js` (no `npm`) because we
   want to maintain it locally; we don't want local adjustments we
   make to get clobbered by an eventual libdef in the FlowTyped
   repo. Delete the metadata lines at the top; they work as a tag on
   libdef contents that come from the FlowTyped repo, which this one
   doesn't [2].

3. Here, we could enter everything in manually, but it turns out
   that DefinitelyTyped has a TypeScript libdef for
   `remotedev-serialize` [3], which we can use as a starting point.
   So, copy that into a temporary local text file as, e.g.,
   libdef.d.ts.

4. Flowgen [4] [5] [6] is a tool for translating from TypeScript to
   Flow. It isn't perfect, and it's transparent about that, which is
   good to see. We just need this single file translated, and it's
   small, so that increases our chances of success. Run `flowgen
   libdef.d.ts`.

5. That output isn't exactly in the form that we want, though. We
   want to put this information in
   `flow-typed/remotedev-serialize_vx.x.x.js` from step 2, in this
   block:

   ```
   declare module 'remotedev-serialize' {
     declare module.exports: any;
   }
   ```

   Copy it into that block, in any case, deleting the `declare
   module.exports: any;` line (we favor ES modules over CommonJS
   modules) and observe the errors.

6. The minimal set of changes to get it working is

   A) replace 'export' with 'declare export' [7]

   B) replace `typeof Immutable` with `any` and remove the Immutable
      import. You can't import types from other libdefs in a libdef
      [8].

7. Step 2 created a lot of extra stubs in case we wanted to make a
   libdef for every single file in
   `node_modules/remotedev-serialize`. We never import directly from
   these other files, so it's fine to just put all the type
   information in a single libdef, as we did in the copy-and-paste
   in step 5. Delete those extra, unnecessary stubs.

[1]: https://flow.org/en/docs/libdefs/#toc-general-best-practices
[2]: https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/Android.20build.3A.20unimodules/near/859855
[3]: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/55ebcedca/types/remotedev-serialize/index.d.ts.
[4]: https://github.com/joarwilk/flowgen
[5]: zulip#3458 (comment)
[6]: https://chat.zulip.org/#narrow/stream/243-mobile-team/topic/Android.20build.3A.20unimodules/near/845802
[7]: https://flow.org/en/docs/libdefs/creation/
[8]: https://github.com/flow-typed/flow-typed/blob/master/CONTRIBUTING.md#dont-import-types-from-other-libdefs
@chrisbobbe
Copy link
Contributor

chrisbobbe commented Jun 5, 2020

So, we've come back to this issue in discussion today.

It was about the lack of support for importing types from third-party code while composing a libdef. In this case, it was about the perfectly valid need to get the ViewProps type from React Native, to use in a react-native-webview libdef. The WebView component's interface allows passing any props you can pass to a View, and that's impossible to express without duplicating tons of RN code from node_modules. The View props are documented here.

@gnprice said:

So:

  • if you import from outside the declare module, it basically just silently doesn't work, behaving like any
  • if you import from inside the declare module, it gives you [an error] unless the thing you're importing is itself defined by a libdef

chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Jun 5, 2020
The latest version FlowTyped has a libdef for is 6, unfortunately.

Ah, well. This is a best effort at replicating the types at
react-native-webview/react-native-webview@c4001338c, tagged as
v7.6.0, the latest 7.x.

An improvement over the FlowTyped libdef is that WebView's props are
now exact. So, e.g., people will be warned not to use the
`useWebKit` prop, which was removed in 7.0.0.

We found it impractical to correctly express the fact that the
`WebView` component also accepts all the props that the `View`
component does. Certainly it seems impossible to do so by importing
things from React Native [1]. So, a partial reimplementation that
allows a `style` prop (typed as an inexact object).

See the entry in `docs/howto/libdefs.md` added in this commit for
more detail.

[1]: zulip#3458 (comment)
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Jun 8, 2020
The latest version FlowTyped has a libdef for is 6, unfortunately.

Ah, well. This is a best effort at replicating the types at
react-native-webview/react-native-webview@c4001338c, tagged as
v7.6.0, the latest 7.x.

An improvement over the FlowTyped libdef is that WebView's props are
now exact. So, e.g., people will be warned not to use the
`useWebKit` prop, which was removed in 7.0.0.

We found it impractical to correctly express the fact that the
`WebView` component also accepts all the props that the `View`
component does. Certainly it seems impossible to do so by importing
things from React Native [1]. So, a partial reimplementation that
allows a `style` prop (typed as an inexact object).

See the entry in `docs/howto/libdefs.md` added in this commit for
more detail.

[1]: zulip#3458 (comment)
gnprice pushed a commit to chrisbobbe/zulip-mobile that referenced this issue Jun 8, 2020
The latest version FlowTyped has a libdef for is 6, unfortunately.

Ah, well. This is a best effort at replicating the types at
react-native-webview/react-native-webview@c4001338c, tagged as
v7.6.0, the latest 7.x.

An improvement over the FlowTyped libdef is that WebView's props are
now exact. So, e.g., people will be warned not to use the
`useWebKit` prop, which was removed in 7.0.0.

We found it impractical to correctly express the fact that the
`WebView` component also accepts all the props that the `View`
component does. Certainly it seems impossible to do so by importing
things from React Native [1]. So, a partial reimplementation that
allows a `style` prop (typed as an inexact object).

See the entry in `docs/howto/libdefs.md` added in this commit for
more detail.

[1]: zulip#3458 (comment)
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Aug 21, 2020
We don't need TypeScript ourselves [1], but we want to upgrade
`eslint-plugin-jest` soon, and a rather distant dependency has a
peer dependency of TypeScript (as we also saw in 01593b3). Here's
the output:

```
warning "eslint-plugin-jest > @typescript-eslint/experimental-utils
> @typescript-eslint/typescript-estree > [email protected]" has unmet
peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev
|| >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta ||
>= 3.7.0-dev || >= 3.7.0-beta".
```

Ah well, at least we can put it in `devDependencies`. And `yarn why
typescript` reveals that `prettier-eslint` and `prettier-eslint-cli`
have both already been bringing it in as their dependency.

[1] zulip#3458 is open to consider migrating from Flow to TypeScript, but
    that's not what this commit is about.
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Sep 16, 2020
We don't need TypeScript ourselves [1], but we want to upgrade
`eslint-plugin-jest` soon, and a rather distant dependency has a
peer dependency of TypeScript (as we also saw in 01593b3). Here's
the output:

```
warning "eslint-plugin-jest > @typescript-eslint/experimental-utils
> @typescript-eslint/typescript-estree > [email protected]" has unmet
peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev
|| >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta ||
>= 3.7.0-dev || >= 3.7.0-beta".
```

Ah well, at least we can put it in `devDependencies`. And `yarn why
typescript` reveals that `prettier-eslint` and `prettier-eslint-cli`
have both already been bringing it in as their dependency.

[1] zulip#3458 is open to consider migrating from Flow to TypeScript, but
    that's not what this commit is about.
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Sep 17, 2020
We don't need TypeScript ourselves [1], but we want to upgrade
`eslint-plugin-jest` soon, and a rather distant dependency has a
peer dependency of TypeScript (as we also saw in 01593b3). Here's
the output:

```
warning "eslint-plugin-jest > @typescript-eslint/experimental-utils
> @typescript-eslint/typescript-estree > [email protected]" has unmet
peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev
|| >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta ||
>= 3.7.0-dev || >= 3.7.0-beta".
```

Ah well, at least we can put it in `devDependencies`. And
`yarn why typescript` reveals that `prettier-eslint` and
`prettier-eslint-cli` have both already been bringing it in as their
dependency. Those libraries are using version 3.9.7, so we set the
range to include that version, so we can avoid pulling in a new
copy.

[1] zulip#3458 is open to consider migrating from Flow to TypeScript, but
    that's not what this commit is about.
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Sep 18, 2020
We don't need TypeScript ourselves [1], but we want to upgrade
`eslint-plugin-jest` soon, and a rather distant dependency has a
peer dependency of TypeScript (as we also saw in 01593b3). Here's
the output:

```
warning "eslint-plugin-jest > @typescript-eslint/experimental-utils
> @typescript-eslint/typescript-estree > [email protected]" has unmet
peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev
|| >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta ||
>= 3.7.0-dev || >= 3.7.0-beta".
```

Ah well, at least we can put it in `devDependencies`. And
`yarn why typescript` reveals that `prettier-eslint` and
`prettier-eslint-cli` have both already been bringing it in as their
dependency. Those libraries are using version 3.9.7, so we set the
range to include that version, so we can avoid pulling in a new
copy.

[1] zulip#3458 is open to consider migrating from Flow to TypeScript, but
    that's not what this commit is about.
chrisbobbe added a commit that referenced this issue Sep 18, 2020
We don't need TypeScript ourselves [1], but we want to upgrade
`eslint-plugin-jest` soon, and a rather distant dependency has a
peer dependency of TypeScript (as we also saw in 01593b3). Here's
the output:

```
warning "eslint-plugin-jest > @typescript-eslint/experimental-utils
> @typescript-eslint/typescript-estree > [email protected]" has unmet
peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev
|| >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta ||
>= 3.7.0-dev || >= 3.7.0-beta".
```

Ah well, at least we can put it in `devDependencies`. And
`yarn why typescript` reveals that `prettier-eslint` and
`prettier-eslint-cli` have both already been bringing it in as their
dependency. Those libraries are using version 3.9.7, so we set the
range to include that version, so we can avoid pulling in a new
copy.

[1] #3458 is open to consider migrating from Flow to TypeScript, but
    that's not what this commit is about.
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Jan 13, 2021
…param.

When we make our own types based on `StackNavigationProp` -- e.g.,
when we define `AppNavigationProp`, in an upcoming commit -- we'll
want those types to be covariant in their own `RouteName` param.

That's because we want, e.g., `AppNavigationProp<'realm'>` to be
recognized as a subtype of
`AppNavigationProp<'realm' | 'account' | …>`, i.e.,
`AppNavigationProp<$Keys<AppNavigatorParamList>>`.

So, start by making `NavigationProp`, `StackNavigationProp`, and
`InexactStackNavigationProp` covariant in their `RouteName` params,
following discussion at
zulip#4393 (comment).

Also make the change for types of navigators other than stack
navigators, across all the libdef files. There's a lot of repetition
because libdef files can't import types from other files [1].

[1] zulip#3458 (comment)
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Jan 13, 2021
…param.

When we make our own types based on `StackNavigationProp` -- e.g.,
when we define `AppNavigationProp`, in an upcoming commit -- we'll
want those types to be covariant in their own `RouteName` param.

That's because we want, e.g., `AppNavigationProp<'realm'>` to be
recognized as a subtype of
`AppNavigationProp<'realm' | 'account' | …>`, i.e.,
`AppNavigationProp<$Keys<AppNavigatorParamList>>`.

So, start by making `NavigationProp`, `StackNavigationProp`, and
`InexactStackNavigationProp` covariant in their `RouteName` params,
following discussion at
zulip#4393 (comment).

Also make the change for types of navigators other than stack
navigators, across all the libdef files. There's a lot of repetition
because libdef files can't import types from other files [1].

[1] zulip#3458 (comment)
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Jan 13, 2021
…param.

When we make our own types based on `StackNavigationProp` -- e.g.,
when we define `AppNavigationProp`, in an upcoming commit -- we'll
want those types to be covariant in their own `RouteName` param.

That's because we want, e.g., `AppNavigationProp<'realm'>` to be
recognized as a subtype of
`AppNavigationProp<'realm' | 'account' | …>`, i.e.,
`AppNavigationProp<$Keys<AppNavigatorParamList>>`.

So, start by making `NavigationProp`, `StackNavigationProp`, and
`InexactStackNavigationProp` covariant in their `RouteName` params,
following discussion at
zulip#4393 (comment).

Also make the change for types of navigators other than stack
navigators, across all the libdef files. There's a lot of repetition
because libdef files can't import types from other files [1].

[1] zulip#3458 (comment)
chrisbobbe added a commit to chrisbobbe/zulip-mobile that referenced this issue Jan 14, 2021
…param.

When we make our own types based on `StackNavigationProp` -- e.g.,
when we define `AppNavigationProp`, in an upcoming commit -- we'll
want those types to be covariant in their own `RouteName` param.

That's because we want, e.g., `AppNavigationProp<'realm'>` to be
recognized as a subtype of
`AppNavigationProp<'realm' | 'account' | …>`, i.e.,
`AppNavigationProp<$Keys<AppNavigatorParamList>>`.

So, start by making `NavigationProp`, `StackNavigationProp`, and
`InexactStackNavigationProp` covariant in their `RouteName` params,
following discussion at
zulip#4393 (comment).

Also make the change for types of navigators other than stack
navigators, across all the libdef files. There's a lot of repetition
because libdef files can't import types from other files [1].

[1] zulip#3458 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants