Search code examples
typescripteslint

Why does TypeScript assume that the document object is always available?


I have the following line of code in a function:

if (!document) return;

For some reason, I am getting the following ESLint error from the official TypeScript rules:

Unnecessary conditional, value is always falsy. eslint(@typescript-eslint/no-unnecessary-condition)

It is not true that document is always truthy. When the code runs on the server, in a Node.js program or as part of a react server component, the document object is not available.

Is there a way to get TypeScript to understand that document may be undefined?

EDIT:

I intend for the code to be able to run in both a node.js (server) environment, and a browser environment. Therefore, env in eslint config is set to { node: true, browser: true, } and lib.dom.d.ts is included via this tsconfig: "lib": ["ESNext", "DOM", "DOM.Iterable"],


Solution

  • The global document variable is declared in lib.dom.d.ts as a Document and not a Document | undefined or anything else.

    Right now you have two choices: include it and accept that document is a Document, or don't include it and then document is not known to be a variable at all. There is at present no facility in TypeScript to "conditionally" include a library, so the use case of having a runtime way of discovering whether the DOM exists is not supported.

    There is an open feature request at microsoft/TypeScript#53971 asking for some better support for such multi-environment code, but it has very little engagement and so I wouldn't expect to see it implemented anytime soon.

    So right now the only way to do what you're asking is not to include lib.dom.d.ts, but instead include or declare your own version of it where document is of type Document | undefined (but you'd have to take care to access it as globalThis.document instead of just document because dereferencing a missing variable results in a ReferenceError and not undefined (or if you want to use document without worrying, you can write globalThis.document ||= undefined at the top of your code, so that document is definitely a global variable no matter what).

    This would be a large undertaking and it's not clear you'd benefit much. For the sake of expedience I'd recommend you just use type assertions or suppress that lint rule for that one line or some other workaround that doesn't actually protect you from a possibly-absent DOM, but at least doesn't give you errors in your environment.