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

No longer possible to use TS API globally in types (5.0.0) #53402

Closed
talks2much opened this issue Mar 20, 2023 · 19 comments
Closed

No longer possible to use TS API globally in types (5.0.0) #53402

talks2much opened this issue Mar 20, 2023 · 19 comments

Comments

@talks2much
Copy link

talks2much commented Mar 20, 2023

Bug Report

Hi! We have TypeScript plugin and after recent update to latest TypeScript version to 5.0.2 we no longer can use ts in types in our files e.g.:

// In TS 4.9.5 we could:

// File: index.ts
import 'typescript/lib/tsserverlibrary' // or ///<ref ...
// File: other.ts
export const decorateLs = (languageService: ts.LanguageService) => {...}

We don't like to rewrite everything to use imports (import('typescript').LanguageService) and we more like the current state of codebase with concise type declarations

🔎 Search Terms

Type import module use globally, define type module globally, declare global type

We tried this workaround:

// File: ts.d.ts
type ts = typeof import('typescript')
// or declare global { ... } export { }

But in other.ts:

type A = ts.LanguageService // Error: 'ts' only refers to a type, but is being used as a namespace here.ts(2702)

But I personally have no idea why it doesn't capture module's type into declared type

//cc @zardoy

@MartinJohns
Copy link
Contributor

TypeScript switched to ECMAScript modules in 5.0. You might be interested in this: https://github.com/microsoft/TypeScript/wiki/API-Breaking-Changes#typescript-50

@talks2much
Copy link
Author

I thought it should affect runtime only. This issue is about types only. Anyway, I have only one question in this issue: how to use types of module x globally? It was possible before, but I'm not sure how to restore this behavior. @MartinJohns Do you know is there a way to do it?

@jakebailey
Copy link
Member

jakebailey commented Mar 21, 2023

Thanks for reporting this; this is something I should have documented as a change to the package. The runtime didn't change, but yeah, the types no longer plop themselves onto the globals. I'll need to see if there's a way to bring that back or not. Probably because I dropped export as namespace ts;, but I don't really want to add that back if it unconditionally declares a global.

What you should be able to do as workaround is to write this in a d.ts file somewhere:

import tsModule = require("typescript");

declare global {
    var ts: typeof tsModule;
}

Or:

import tsModule = require("typescript/lib/tsserverlibrary");

declare global {
    var ts: typeof tsModule;
}

declare global { var ts: typeof import("typescript"); } or similar may work too.

Out of curiosity, what are you trying to do where importing is undesirable? I'm confused by your example; tsserverlibrary and typescript are not equivalent and should not be interchanged. I hope you weren't using import 'typescript/lib/tsserverlibrary' as a way to bring typescript in scope.

@jakebailey
Copy link
Member

If what you're doing is this: https://github.com/zardoy/typescript-vscode-plugins/blob/9641211286cc0764fa503f86a105082a5feff08e/typescript/src/definitions.ts

Then yeah, this doesn't seem like a good idea at all... Why not import it like everything else? This "global" behavior is only applicable to the web.

@jakebailey
Copy link
Member

I would strongly suggest an approach more like: https://github.com/mrmckeb/typescript-plugin-css-modules/pull/213/files

@zardoy
Copy link
Contributor

zardoy commented Mar 21, 2023

If what you're doing is this: zardoy/typescript-vscode-plugins@9641211/typescript/src/definitions.ts

This is the biggest plugin we have, I also noticed this behavior in of my private plugins, but yeah, the same issue lays here.

I would strongly suggest an approach more like: mrmckeb/typescript-plugin-css-modules#213 (files)

I already thought about adding import type ts from 'typescript' to every file, but I'll have eslint rule setup to restrict typescript imports to ensure typescript module doesn't get bundled. Of course I could change this setup to include better checks, but if there is a way to declare this type globally, I'd do it instead, so I don't need to have a template for creating new files.
Also I don't want to have named typescript imports, because I have a lot local types with conflicting names in my codebase (e.g. TextChange), so I disable typescript auto-import and prefer to access needed ts types from global ts type instead e.g. ts.Symbol. I've heard you migrated TS codebase to named imports, but in my case import suggestions are coming not only from TS codebase, but also from my codebase.

btw @talks2much in mentioned file we have typescript import, it's type-only, could I you help setup eslint rule as well?

I'll need to see if there's a way to bring that back or not

To be honest, not sure you should bring old behavior when type is declared in global scope implicitly. I think if we found workaround we wouldn't raise this issue. Now I wonder if this is a TypeScript limitation?

What you should be able to do as workaround is to write this in a d.ts file somewhere:

import tsModule = require("typescript");

declare global {
    var ts: typeof tsModule;
}

As said in issue body, we already tried this workaround, it declared only variable globally. @talks2much do you have any additional ideas?

@zardoy
Copy link
Contributor

zardoy commented Mar 21, 2023

Also thanks for opening this, didn't have much time to prioritize it :)

I personally also just tried this:

// ts.d.ts
export * from 'typescript'
export as namespace ts;

But it gives the following error:

Module '"C:/Users/Vitaly/Documents/samples/ts-module-empty/node_modules/.pnpm/typescript@5.0.2/node_modules/typescript/lib/typescript"' uses 'export =' and cannot be used with 'export *'.

(which doesn't seem to be logical for d.ts)

@jakebailey
Copy link
Member

What about:

import ts = require("typescript");
export = ts;
export as namespace ts;

@jakebailey
Copy link
Member

But, if you are a language service plugin, you definitely want tsserverlibrary.

@jakebailey
Copy link
Member

You could also just patch-package the d.ts file in our package to add export as namespace ts.

@zardoy
Copy link
Contributor

zardoy commented Mar 21, 2023

But, if you are a language service plugin, you definitely want tsserverlibrary.

Exactly, I suppose it's just a mistake in the body of this issue.

Also wanted to answer this:

This "global" behavior is only applicable to the web.

In this plugin I have tricky way of using ts variable globally. Since all the code gets bundled into one file, I can write let ts on top of it, assign when plugin is created, and use ts variable globally in any methods (I believe navtree module using a similar thing with context variables)

What about:

import ts = require("typescript");
export = ts;
export as namespace ts;

Aaand it works! I just also added declare global { let ts }. I also see it works only with TypeScript, thank you so much!

This seems to be the only way to declare module type globally.

This is exactly what we have been looking for for a long time.

@talks2much
Copy link
Author

But, if you are a language service plugin, you definitely want tsserverlibrary.

Exactly, I suppose it's just a mistake in the body of this issue.

Indeed, but I didn't know there is a difference between typescript and typescript/lib/tsserverlibrary definitions.

This seems to be the only way to declare module type globally.

Hm, probably you're right. The workaround look insanely good. Though why umd-specific feature is the only way to do it? This seems to be illogical to me:

// I can do this
type LS = import('typescript').LanguageService
// but, this is an error
type ts = import('typescript') // Module 'typescript' does not refer to a type, but is used as a type here. Did you mean 'typeof import('typescript')'?
type LS = ts.LanguageService

@jakebailey am I right that this is a TypeScript limitation there? E.g. why module 'typescript' can't refer to a type?

@jakebailey
Copy link
Member

You can do type ts = typeof import("typescript"), but that just gives you something where you can write ts["createLanguageService"] because that's an object type. You just can't use the dotted notation for anything but actual imports.

kodiakhq bot pushed a commit to vercel/next.js that referenced this issue Jun 8, 2023
We need to upgrade TypeScript to land the following fixes:

- #50289
- #48018

And it should fix async components:
- https://twitter.com/sebsilbermann/status/1664356039876124672

Also see this related TS 5.0 issue:

- microsoft/TypeScript#53402
@zardoy
Copy link
Contributor

zardoy commented Jul 23, 2024

@jakebailey We were using your pattern from #53402 (comment). and it was brilliant, worked perfectly! It still worked when we were using TS 5.5 beta and RC, but broke after official release 5.0:

//ts.d.ts
// eslint-disable-next-line @typescript-eslint/no-require-imports
import ts = require('typescript')
export = ts
export as namespace ts
declare global {
    const ts: typeof import('typescript')
}
ts.d.ts:4:21 - error TS2451: Cannot redeclare block-scoped variable 'ts'.

4 export as namespace ts
                      ~~

  a.d.ts:7:11
    7     const ts: typeof import('three')
                ~~
    'ts' was also declared here.

If I remove const ts: typeof import('typescript') the global type reference works fine eg I can still use type ts globally.

So as I understand now its now only possible to either use the type or variable with the same name globally, but not both?

@jakebailey
Copy link
Member

I'm not sure that using both export = ts; export as namespace ts' and declare const ts: typeof import("typescript") is what I suggested... I would think you'd only be able to use one, that is "patch in export as namespace ts" or "add a global declaration".

I'm also unsure what you mean about "using 5.5 beta and RC but broke after 5.0"; do you mean after 5.5's full release? Consider bisecting with https://www.npmjs.com/package/every-ts if so.

@zardoy
Copy link
Contributor

zardoy commented Jul 23, 2024

do you mean after 5.5's full release?

Oh yes, I apologize for the confusion, I meant the 5.5 release. Thanks helping!

And yes, having both global declaration and global type definition e.g. so I could use type A = ts.Node without import was super useful, will try to find the commit breaking this.

@jakebailey
Copy link
Member

I would guess that #58326 is what changed this, but that was in the RC... In any case, not 100% clear why you'd both declare a global explicitly, but then also have export as namespace ts.

@zardoy
Copy link
Contributor

zardoy commented Jul 23, 2024

export as namespace ts

This allows me to use the type globally (as you suggested above). Having just declare const would not allow me to use the type alias globally (like in type A = ts.Node - this would still need an import declaration). But I was able to use and type alias and the variable globally at the same time which was super convenient 👍

but that was in the RC

My bad, you are right, it was already broken in rc but was ok only during the beta.

@zardoy
Copy link
Contributor

zardoy commented Jul 23, 2024

#58326 (review)

Oh so they were aware of that breakage 😥

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

No branches or pull requests

4 participants