remix: Typescript compilation fails

What version of Remix are you using?

1.7.2, Node v16.18.0, npm 8.19.2

Steps to Reproduce

Run npx create-remix@latest, pick Just the basics, any template, Typescript.

Run npx tsc --build inside the app folder.

Expected Behavior

Typescript compiles without errors.

Actual Behavior

Output with Remix App Server, Express, Architect, Fly, Netlify and Vercel templates:

Expand
node_modules/@remix-run/node/dist/upload/fileUploadHandler.d.ts:47:22 - error TS2420: Class 'NodeOnDiskFile' incorrectly implements interface 'File'.
  Property 'prototype' is missing in type 'NodeOnDiskFile' but required in type 'File'.

47 export declare class NodeOnDiskFile implements File {
                        ~~~~~~~~~~~~~~
  
  node_modules/typescript/lib/lib.dom.d.ts:2491:5
    2491     prototype: Blob;
             ~~~~~~~~~
    'prototype' is declared here.
  
  
Found 1 error.

Output with Cloudflare Pages and Workers templates:

Expand
node_modules/typescript/lib/lib.dom.d.ts:25:1 - error TS6200: Definitions of the following identifiers conflict with those in another file: AbortController, AbortSignal, Blob, BodyInit, ByteLengthQueuingStrategy, Cache, CacheStorage, CloseEvent, CountQueuingStrategy, Crypto, CryptoKey, DOMException, Event, EventListener, EventListenerOrEventListenerObject, EventTarget, File, FormData, Headers, HeadersInit, MessageEvent, Navigator, PromiseRejectionEvent, ReadableByteStreamController, ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, ReadableStreamReadResult, Request, Response, StreamPipeOptions, SubtleCrypto, TextDecoder, TextDecoderStream, TextEncoder, TextEncoderStream, TransformStream, start, transform, flush, URL, URLSearchParams, write, abort, close, pull, cancel, WebSocket, WebSocketEventMap, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, caches, console, crypto, navigator, origin, self

25 interface AddEventListenerOptions extends EventListenerOptions {
   ~~~~~~~~~

  node_modules/@cloudflare/workers-types/index.d.ts:4:1
    4 declare class AbortController {
      ~~~~~~~
    Conflicts are in this file.

node_modules/typescript/lib/lib.dom.d.ts:666:5 - error TS2687: All declarations of 'kty' must have identical modifiers.

666     kty?: string;
        ~~~

node_modules/typescript/lib/lib.dom.d.ts:918:5 - error TS2687: All declarations of 'data' must have identical modifiers.

918     data?: T;
        ~~~~

node_modules/typescript/lib/lib.dom.d.ts:1736:5 - error TS2687: All declarations of 'read' must have identical modifiers.

1736     read?: number;
         ~~~~

node_modules/typescript/lib/lib.dom.d.ts:1737:5 - error TS2687: All declarations of 'written' must have identical modifiers.

1737     written?: number;
         ~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:3653:11 - error TS2430: Interface 'Comment' incorrectly extends interface 'CharacterData'.
  Types of property 'after' are incompatible.
    Type '(content: Content, options?: ContentOptions | undefined) => Comment' is not assignable to type '(...nodes: (string | Node)[]) => void'.
      Types of parameters 'content' and 'nodes' are incompatible.
        Type 'string | Node' is not assignable to type 'Content'.
          Type 'Node' is not assignable to type 'Content'.

3653 interface Comment extends CharacterData {
               ~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:4621:101 - error TS2344: Type 'HTMLElementTagNameMap[K]' does not satisfy the constraint 'Element'.
  Type 'HTMLElement | HTMLAnchorElement | HTMLAreaElement | HTMLAudioElement | HTMLBaseElement | ... 57 more ... | HTMLPictureElement' is not assignable to type 'Element'.
    Type 'HTMLSelectElement' is not assignable to type 'Element'.
      Types of property 'remove' are incompatible.
        Type '{ (): void; (index: number): void; }' is not assignable to type '() => Element'.

4621     getElementsByTagName<K extends keyof HTMLElementTagNameMap>(qualifiedName: K): HTMLCollectionOf<HTMLElementTagNameMap[K]>;
                                                                                                         ~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:4859:11 - error TS2430: Interface 'Element' incorrectly extends interface 'ChildNode'.
  Types of property 'after' are incompatible.
    Type '(content: Content, options?: ContentOptions | undefined) => Element' is not assignable to type '(...nodes: (string | Node)[]) => void'.
      Types of parameters 'content' and 'nodes' are incompatible.
        Type 'string | Node' is not assignable to type 'Content'.

4859 interface Element extends Node, ARIAMixin, Animatable, ChildNode, InnerHTML, NonDocumentTypeChildNode, ParentNode, Slottable {
               ~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:4859:11 - error TS2430: Interface 'Element' incorrectly extends interface 'ParentNode'.
  Types of property 'append' are incompatible.
    Type '(content: Content, options?: ContentOptions | undefined) => Element' is not assignable to type '(...nodes: (string | Node)[]) => void'.
      Types of parameters 'content' and 'nodes' are incompatible.
        Type 'string | Node' is not assignable to type 'Content'.

4859 interface Element extends Node, ARIAMixin, Animatable, ChildNode, InnerHTML, NonDocumentTypeChildNode, ParentNode, Slottable {
               ~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:4891:14 - error TS2687: All declarations of 'tagName' must have identical modifiers.

4891     readonly tagName: string;
                  ~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:4910:101 - error TS2344: Type 'HTMLElementTagNameMap[K]' does not satisfy the constraint 'Element'.
  Type 'HTMLElement | HTMLAnchorElement | HTMLAreaElement | HTMLAudioElement | HTMLBaseElement | ... 57 more ... | HTMLPictureElement' is not assignable to type 'Element'.

4910     getElementsByTagName<K extends keyof HTMLElementTagNameMap>(qualifiedName: K): HTMLCollectionOf<HTMLElementTagNameMap[K]>;
                                                                                                         ~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:7769:11 - error TS2430: Interface 'HTMLSelectElement' incorrectly extends interface 'HTMLElement'.

7769 interface HTMLSelectElement extends HTMLElement {
               ~~~~~~~~~~~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:7769:11 - error TS2430: Interface 'HTMLSelectElement' incorrectly extends interface 'HTMLElement'.
  The types returned by 'remove()' are incompatible between these types.
    Type 'void' is not assignable to type 'Element'.

7769 interface HTMLSelectElement extends HTMLElement {
               ~~~~~~~~~~~~~~~~~

node_modules/typescript/lib/lib.dom.d.ts:13875:11 - error TS2430: Interface 'Text' incorrectly extends interface 'CharacterData'.
  Types of property 'after' are incompatible.
    Type '(content: Content, options?: ContentOptions | undefined) => Text' is not assignable to type '(...nodes: (string | Node)[]) => void'.
      Types of parameters 'content' and 'nodes' are incompatible.
        Type 'string | Node' is not assignable to type 'Content'.

13875 interface Text extends CharacterData, Slottable {
                ~~~~

node_modules/@types/react/index.d.ts:2854:78 - error TS2344: Type 'HTMLSelectElement' does not satisfy the constraint 'HTMLElement'.
  The types returned by 'remove()' are incompatible between these types.
    Type 'void' is not assignable to type 'Element'.

2854         select: DetailedHTMLFactory<SelectHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>;
                                                                                  ~~~~~~~~~~~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:4:1 - error TS6200: Definitions of the following identifiers conflict with those in another file: AbortController, AbortSignal, Blob, BodyInit, ByteLengthQueuingStrategy, Cache, CacheStorage, CloseEvent, CountQueuingStrategy, Crypto, CryptoKey, DOMException, Event, EventListener, EventListenerOrEventListenerObject, EventTarget, File, FormData, Headers, HeadersInit, MessageEvent, Navigator, PromiseRejectionEvent, ReadableByteStreamController, ReadableStream, ReadableStreamBYOBReader, ReadableStreamBYOBRequest, ReadableStreamDefaultController, ReadableStreamDefaultReader, ReadableStreamReadResult, Request, Response, StreamPipeOptions, SubtleCrypto, TextDecoder, TextDecoderStream, TextEncoder, TextEncoderStream, TransformStream, start, transform, flush, URL, URLSearchParams, write, abort, close, pull, cancel, WebSocket, WebSocketEventMap, WritableStream, WritableStreamDefaultController, WritableStreamDefaultWriter, caches, console, crypto, navigator, origin, self

4 declare class AbortController {
  ~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:25:1
    25 interface AddEventListenerOptions extends EventListenerOptions {
       ~~~~~~~~~
    Conflicts are in this file.

node_modules/@cloudflare/workers-types/index.d.ts:108:12 - error TS2717: Subsequent property declarations must have the same type.  Property 'body' must be of type 'ReadableStream<Uint8Array> | null', but here has type 'ReadableStream<any> | null'.

108   readonly body: ReadableStream | null;
               ~~~~

  node_modules/typescript/lib/lib.dom.d.ts:2506:14
    2506     readonly body: ReadableStream<Uint8Array> | null;
                      ~~~~
    'body' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:517:3 - error TS2687: All declarations of 'tagName' must have identical modifiers.

517   tagName: string;
      ~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:518:12 - error TS2717: Subsequent property declarations must have the same type.  Property 'attributes' must be of type 'NamedNodeMap', but here has type 'IterableIterator<string[]>'.

518   readonly attributes: IterableIterator<string[]>;
               ~~~~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:4860:14
    4860     readonly attributes: NamedNodeMap;
                      ~~~~~~~~~~
    'attributes' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:520:12 - error TS2717: Subsequent property declarations must have the same type.  Property 'namespaceURI' must be of type 'string | null', but here has type 'string'.

520   readonly namespaceURI: string;
               ~~~~~~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:4874:14
    4874     readonly namespaceURI: string | null;
                      ~~~~~~~~~~~~
    'namespaceURI' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:591:5 - error TS2315: Type 'EventListener' is not generic.

591 > = EventListener<EventType> | EventListenerObject<EventType>;
        ~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:599:14 - error TS2315: Type 'EventListenerOrEventListenerObject' is not generic.

599     handler: EventListenerOrEventListenerObject<EventMap[Type]>,
                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:604:14 - error TS2315: Type 'EventListenerOrEventListenerObject' is not generic.

604     handler: EventListenerOrEventListenerObject<EventMap[Type]>,
                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:862:3 - error TS2687: All declarations of 'kty' must have identical modifiers.

862   kty: string;
      ~~~

node_modules/@cloudflare/workers-types/index.d.ts:862:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'kty' must be of type 'string | undefined', but here has type 'string'.

862   kty: string;
      ~~~

  node_modules/typescript/lib/lib.dom.d.ts:666:5
    666     kty?: string;
            ~~~
    'kty' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:1006:3 - error TS2687: All declarations of 'data' must have identical modifiers.

1006   data: ArrayBuffer | string;
       ~~~~

node_modules/@cloudflare/workers-types/index.d.ts:1006:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'data' must be of type 'T | undefined', but here has type 'string | ArrayBuffer'.

1006   data: ArrayBuffer | string;
       ~~~~

  node_modules/typescript/lib/lib.dom.d.ts:918:5
    918     data?: T;
            ~~~~
    'data' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:1317:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'redirect' must be of type 'RequestRedirect | undefined', but here has type 'string | undefined'.

1317   redirect?: string;
       ~~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:1552:5
    1552     redirect?: RequestRedirect;
             ~~~~~~~~
    'redirect' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:1802:3 - error TS2687: All declarations of 'read' must have identical modifiers.

1802   read: number;
       ~~~~

node_modules/@cloudflare/workers-types/index.d.ts:1802:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'read' must be of type 'number | undefined', but here has type 'number'.

1802   read: number;
       ~~~~

  node_modules/typescript/lib/lib.dom.d.ts:1736:5
    1736     read?: number;
             ~~~~
    'read' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:1803:3 - error TS2687: All declarations of 'written' must have identical modifiers.

1803   written: number;
       ~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:1803:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'written' must be of type 'number | undefined', but here has type 'number'.

1803   written: number;
       ~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:1737:5
    1737     written?: number;
             ~~~~~~~
    'written' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:1890:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'readableType' must be of type 'undefined', but here has type 'string | undefined'.

1890   readableType?: string;
       ~~~~~~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:1770:5
    1770     readableType?: undefined;
             ~~~~~~~~~~~~
    'readableType' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:1891:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'writableType' must be of type 'undefined', but here has type 'string | undefined'.

1891   writableType?: string;
       ~~~~~~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:1773:5
    1773     writableType?: undefined;
             ~~~~~~~~~~~~
    'writableType' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:2001:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'type' must be of type 'undefined', but here has type 'string | undefined'.

2001   type?: string;
       ~~~~

  node_modules/typescript/lib/lib.dom.d.ts:1798:5
    1798     type?: undefined;
             ~~~~
    'type' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:2009:3 - error TS2717: Subsequent property declarations must have the same type.  Property 'type' must be of type 'undefined', but here has type 'string | undefined'.

2009   type?: string;
       ~~~~

  node_modules/typescript/lib/lib.dom.d.ts:1806:5
    1806     type?: undefined;
             ~~~~
    'type' was also declared here.

node_modules/@cloudflare/workers-types/index.d.ts:2024:33 - error TS2508: No base constructor has the specified number of type arguments.

2024 declare class WebSocket extends EventTarget<WebSocketEventMap> {
                                     ~~~~~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:2048:50 - error TS2508: No base constructor has the specified number of type arguments.

2048 declare abstract class WorkerGlobalScope extends EventTarget<WorkerGlobalScopeEventMap> {}
                                                      ~~~~~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:2093:12 - error TS2315: Type 'EventListenerOrEventListenerObject' is not generic.

2093   handler: EventListenerOrEventListenerObject<WorkerGlobalScopeEventMap[Type]>,
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/@cloudflare/workers-types/index.d.ts:2130:12 - error TS2315: Type 'EventListenerOrEventListenerObject' is not generic.

2130   handler: EventListenerOrEventListenerObject<WorkerGlobalScopeEventMap[Type]>,
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


Found 40 errors.

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 31
  • Comments: 25 (11 by maintainers)

Commits related to this issue

Most upvoted comments

Downgrading typescript to 4.8 fixes the issue for me. So seems likely to be a bug introduced sometime in 4.9.x.


Workaround is to force TS 4.8: npm install typescript@4.8

@lpsinger unfortunately, prototype = {}; is not enough. tsc will then complain that prototype is missing properties from Blob:

Screenshot 2023-07-03 at 1 28 20 PM

To recap for any onlookers, implements File does not require prototype, so any attempts to patch prototype are a workaround at best.

Looks like TS has included this regression as part of their 5.1.0 milestone, though not sure about the timing of that since 5.1.0 is already out? Maybe 5.1 milestone includes things to fix before 5.2? 🤷

If you don’t want to downgrade Typescript but want to stop the error and get a nudge when this is fixed then put /* @ts-expect-error */ immediately before the line erroring and it will stop complaining. Once it is fixed and stops erroring, it will start complaining again (because there is no error anymore, as expected), just remove the @ts-expect-error and you have type checking back.

You can leave your self a reminder of why too if you like:

  /* @ts-expect-error Temporary workaround for https://github.com/remix-run/remix/issues/4371 */

Just be careful if you have multiple type checks on the next line as it will stop errors for any of the checks.

Reference: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-9.html#-ts-expect-error-comments

I successfully reproduced the issue with NodeFileOnDisk 👍

However, I’m not sure we will be able to fix errors from Cloudflare pages & workers, as they seem to emanate from the @cloudflare packages. Could you fill a report to the relevant repo?

Confirming that Typescript 4.9+ is an issue

Steps to Reproduce

Created a fresh remix app. I chose the remix express template Ran the npm run typecheck and it would fail if I used typescript 4.9.5

CleanShot 2023-03-10 at 12 13 32@2x

After downgrading to typescript 4.8.4 the typecheck command worked: CleanShot 2023-03-10 at 12 14 37@2x

@akomm I think the issue is that we do want to use implements but targeting the TS built-in lib.dom.d.ts definition of File (and Blob). extends is semantically different so we’d prefer not to use extends.

Talked to @jacob-ebey about this a couple days ago and we didn’t know of a good way to disambiguate the different Files in the implements declaration, so the best workaround we came up with was to use extends and simply have a bunch of unimplemented or pass-through methods/properties. Like I said, not ideal and we’d prefer something that lets us use implements instead, but couldn’t find any other workarounds or fixes.

@pcattori

A small change in the TS Playground example makes the error appear.

I assume the Playground loads node library once it sees any usage of its core modules. You can change “buffer” with “path” or any other core module of node, the effect is the same.

I’m getting this with a tsc -b on my machine.

node_modules/@remix-run/node/dist/upload/fileUploadHandler.d.ts:47:22 - error TS2420: Class 'NodeOnDiskFile' incorrectly implements interface 'File'.
  Property 'prototype' is missing in type 'NodeOnDiskFile' but required in type 'File'.

47 export declare class NodeOnDiskFile implements File {
                        ~~~~~~~~~~~~~~

  node_modules/typescript/lib/lib.dom.d.ts:2572:5
    2572     prototype: Blob;
             ~~~~~~~~~
    'prototype' is declared here.

Running TS 4.9.4 and Remix 1.9.0

@depsimon yes this is terrible as it affects not just libs, but simply all d.ts files, including those of your own project, not just node_modules or the default TS “libs”.