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

Experimental typescript support #19842

Merged
merged 2 commits into from
Apr 10, 2024

Conversation

allisonkarlitskaya
Copy link
Member

@allisonkarlitskaya allisonkarlitskaya commented Jan 15, 2024

I want to use typescript in an upcoming test page in the playground. Let's lay some foundations.

I ported hooks.js to typescript because:

  • I use it from my page
  • it wasn't too hard
  • proof of concept to make sure nothing breaks

I didn't port cockpit.js because:

  • that's gonna be a lot of work

In any case, if everything is working correctly, this shouldn't actually change anything.

For the record:

31M	node_modules/typescript
548K	node_modules/@types
32M	total

The @types includes are fairly harmless, but I'm a bit more worried about adding typescript itself. That's a ~7% increase in the size of our node_modules. We could alternatively use the version of typescript available in Fedora and make sure to install it somewhere (tasks container, dev depenency during package builds, whatever) to make sure test/static-code can run it in CI.

todo

  • esbuild transparently supports .ts, but make sure this works with a webpack project, too (cockpit-composer?) we don't care about webpack anymore

@jelly
Copy link
Member

jelly commented Jan 15, 2024

This is interesting like mypy is :-)

Some general note/thoughts:

I would love to have some IDE integration working with typescript as I am silly and sometimes type binary : "raw" on cockpit.channel which supports no such option. For that typescript would be great! So I would applaud typing cockpit.js and pkg/lib. I am not sure yet how I feel about normal pages/components.

The current approach of the PR requires files to be whitelisted to be linted and to be typescript, this seems suboptimal. I would love to already start using the types available to use so what I did was:

checkJs: true

Ok that does nothing, because we allowlist files, so I added pkg/shell/superuser.jsx that gives ~ 300 errors due to strict: true. Remove that and it becomes more reasonable:

Some really interesting things:

This is not typed, but already shows a "bug"

pkg/shell/superuser.jsx:127:31 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'SetStateAction<boolean>'.

127                     setMethod(ids[0]);
                                  ~~~~~~

PattternFly is typescript so we get interesting errors:

pkg/shell/superuser.jsx:233:16 - error TS2322: Type 'string' is not assignable to type '"danger" | ComponentType<any> | "success" | "warning" | "info" | "custom"'.                    [34/1288]

233                titleIconVariant={title_icon}
                   ~~~~~~~~~~~~~~~~

  node_modules/@patternfly/react-core/dist/esm/components/Modal/Modal.d.ts:67:5
    67     titleIconVariant?: 'success' | 'danger' | 'warning' | 'info' | 'custom' | React.ComponentType<any>;
           ~~~~~~~~~~~~~~~~

when something is half typed or is imported without typing typescript lang server is not happy which is a big blocker :(

pkg/shell/machines/machines.js:726:31 - error TS2339: Property 'dbus' does not exist on type 'typeof import("/home/jelle/projects/cockpit/typescript/pkg/lib/cockpit")'.

726         const proxy = cockpit.dbus(null, { bus: "internal" }).proxy("cockpit.Machines", "/machines");
pkg/shell/superuser.jsx:23:28 - error TS2307: Cannot find module 'dialogs.jsx' or its corresponding type declarations.

23 import { useDialogs } from "dialogs.jsx";

I would need to do a bit more investigation, but it would be neat if we can maybe start whitelisting directories for typescript, the import issue seems to mostly block that but maybe there is a way around it?

@@ -262,10 +266,10 @@ export function useObject(create, destroy, deps, comps) {

destroy_ref.current = destroy;
useEffect(() => {
return () => destroy_ref.current?.(ref.current);
return () => { destroy_ref.current?.(ref.current!) };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the bang needed for?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The destroy function takes a non-null pointer, but the current value is null-typed. The ! is an assertion which means "I promise this isn't null".

pkg/lib/hooks.ts Outdated Show resolved Hide resolved
pkg/lib/cockpit.d.ts Outdated Show resolved Hide resolved
pkg/lib/cockpit.d.ts Outdated Show resolved Hide resolved
@allisonkarlitskaya
Copy link
Member Author

Here's a few changes after feedback (as well as fixing the problems identified above).

The main changes:

  • less explicit handling of files in pkg/lib (although I'm not super happy about the solution).
  • no need to list every file anymore — we do a glob
  • checking of JS is enabled by default, so opening an editor on a .js or .jsx file is bound to be noisy. You can help reduce the noise by contributing to type annotations. When we run tsc from test/static-code we disable --checkJs.

Now, running tsc takes ~18s and produces a ridiculous amount of errors. Opening a file in the editor only takes ~3s before getting useful feedback, though. And tsc --checkJs false (which we call from test/static-code) takes less than 2 seconds.

@allisonkarlitskaya
Copy link
Member Author

It hasn't been spoken out loud yet, so I'll say it now: the eventual goal is to have cockpit.ts. It's going to take a while, but this is a good start to give us a framework in which we could think about making it happen.

@jelly
Copy link
Member

jelly commented Jan 16, 2024

It hasn't been spoken out loud yet, so I'll say it now: the eventual goal is to have cockpit.ts. It's going to take a while, but this is a good start to give us a framework in which we could think about making it happen.

I think the team agrees on this. See this comment of @martinpitt and btw someone did attempt some type hints in the past maybe it's usable?

@jelly
Copy link
Member

jelly commented Jan 16, 2024

From our meeting:

  • Webpack - still required, when do we drop it?
  • Cockpit-composer - still uses webpack, does that work out of the box with hooks.ts?

Copy link
Member

@mvollmer mvollmer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks harmless enough to me.

@allisonkarlitskaya
Copy link
Member Author

Let's set this aside for now. cockpit-project/cockpit-files#211 is where we're going to do this for the time being, but we can easily come back to this PR later.

Add the typescript compiler to our devel dependencies and run it from
`test/static-code`.  We add an initial cockpit.d.ts and a tsconfig.json
which will get `tsc` to report errors against any `.ts` or `.tsx` files.
Add some type annotations to make it compile with strict mode enabled.
* Our main build is via `esbuild` which can handle `.ts`, but doesn't do any
* checking on its own. To typecheck, run `tsc`.
*
* Extended JSON — comments and trailing commas are good here.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, does that not support something more expressive, like yaml..

Copy link
Member

@martinpitt martinpitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of this is still under debate, but as it's completely on the "lint" side for now, it remains fungible. So I'm fine with landing that, and changing it as we need it -- at least we get the bits into node_modules/ for easy experimentation.

Thanks!

],
"noEmit": true, // we only use `tsc` for type checking
"skipLibCheck": true, // don't check node_modules
"strict": true,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose that's still under debate? It wasn't at all clear to me after the meeting whether we should turn this on or off.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the conclusion was that we want strict mode for .ts and a non-strict mode for .js coming along in some future PR.

@@ -262,10 +273,10 @@ export function useObject(create, destroy, deps, comps) {

destroy_ref.current = destroy;
useEffect(() => {
return () => destroy_ref.current?.(ref.current);
return () => { destroy_ref.current?.(ref.current!) };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does it need to be wrapped into {} now? Does the current() call return something non-void?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the ! somehow caused syntactic problems if it wasn't in a block. I honestly don't remember: it was a long time ago.

@allisonkarlitskaya allisonkarlitskaya merged commit 3ec539a into cockpit-project:main Apr 10, 2024
73 of 74 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants