103

I try to convert an existing project to use Typescript and I have problems doing so with my testing setup.

I had a setup file for my tests that sets up jsdom so that all my DOM interacting code works during my tests. Using Typescript (ts-node with mocha) I always get errors like this:

Property 'window' does not exist on type 'Global'.

To prevent this I tried patching the NodeJS.Global interface like this:

declare namespace NodeJS{
  interface Global {
    document: Document;
    window: Window;
    navigator: Navigator;
  }
}

But this didn't change anything.

How do I enable those browser properties on the NodeJS global variable?

Extras:

This is my mocha setup.ts:

import { jsdom, changeURL } from 'jsdom';

const exposedProperties = ['window', 'navigator', 'document'];

global.document = jsdom('');
global.window = global.document.defaultView;
Object.keys(global.document.defaultView).forEach((property) => {
  if (typeof global[property] === 'undefined') {
    exposedProperties.push(property);
    global[property] = global.document.defaultView[property];
  }
});

global.navigator = {
  userAgent: 'node.js',
};

changeURL(global.window, 'http://example.com/');
Endless
  • 34,080
  • 13
  • 108
  • 131
fahrradflucht
  • 1,563
  • 3
  • 14
  • 22
  • Can't be sure it's the right answer, but having gone through similar pain, it turns out that the file within which you extend the NodeJS.Global interface must be all lowercase, or typings will ignore it. Also, it can't have any import or export statements at the top - or it will be seen as a module rather than a typings file. – Izhaki Dec 15 '16 at 10:27
  • 5
    none of these answers seem to work, does anyone have an answer in 2018? – Alexander Mills Mar 15 '18 at 04:13

9 Answers9

148

Original Answer To Avoid Error

Put this at the top of your typescript file

const globalAny:any = global;

Then use globalAny instead.

globalAny.document = jsdom('');
globalAny.window = global.document.defaultView;

Updated Answer To Maintain Type Safety

If you want to keep your type safety, you can augment the existing NodeJS.Global type definition.

You need to put your definition inside the global scope declare global {...}

Keep in mind that the typescript global scope is not the same as the NodeJS interface Global, or the node global property called global of type Global...

declare global {
  namespace NodeJS {
    interface Global {
       document: Document;
       window: Window;
       navigator: Navigator;
    } 
  }
}
Steven Spungin
  • 27,002
  • 5
  • 88
  • 78
  • 12
    Removing typings by casting `any` really goes against why Typescript is used in the first place. Infact if you're linting TSLint has an option specifically to prevent this: https://palantir.github.io/tslint/rules/no-any – Will Squire Jan 20 '19 at 21:59
  • 1
    @willsquire I updated the solution with a type-safe option. The main reason this did not work for OP is included. – Steven Spungin Jun 24 '19 at 11:35
  • 2
    This declaration isn't compatible with eslint rule of not using namespaces. Look at my answer below for a more interesting approach. – José Cabo Dec 14 '19 at 15:41
  • The question has nothing to do with eslint, and rules are a matter of personal style. – Steven Spungin Dec 14 '19 at 17:16
  • 2
    ESLint rules are personal, true, but everyone nowadays typically will use all default rules as a base and disable or modify these than they're not comfortable with. My solution below covers all the cases and IMO it's more semantic therefore making the answer more complete. – José Cabo Feb 04 '20 at 14:58
  • it should be: `globalAny.window = globalAny.document.defaultView` otherwise again you get typescript error – Greg Wozniak May 14 '20 at 16:56
  • Using the updated answer gives : `error TS2304: Cannot find name 'Document'. Cannot find name 'Window'. Cannot find name 'Navigator'.` – Jeremy Thille Jun 22 '21 at 07:30
  • @JeremyThille sounds like an issue including a library in your typescript config. Those are all in the Dom library. – Steven Spungin Jun 22 '21 at 14:34
57

In addition to other answers, you can also simply cast global directly at the assignment site:

(global as any).myvar = myvar;
lleaff
  • 4,249
  • 17
  • 23
  • 6
    This is contradictory to typescript usage since it neutral type checking, the first principle usage of the typescript. – Shahin Ghasemi Jun 11 '20 at 12:17
  • 5
    @ShahinGhasemi Really, I came to a point where I don't care. I am so tired of spending hours, and even entire days, just to make my apps compile and run. I used to develop and produce, now I spend more and more days solving typing errors, compilation issues and dependencies incompatinilities. I don't care if this goes against a paradigm or defeats a purpose. All I want at some point is my bloody app to compile and finally get some rest. This is the only solution that worked for me. All others just lead to new errors and issues. – Jeremy Thille Jun 22 '21 at 07:48
  • @JeremyThille I really DO hear you, I think we are overcomplicating web development like if we were all trying to make the next huge application when maybe we are building a simple marketing site! – Mattia Rasulo Mar 31 '23 at 18:45
26

I fixed this problem by doing this...

export interface Global {
  document: Document;
  window: Window;
}

declare var global: Global;
Shawn
  • 269
  • 3
  • 2
  • 9
    `export interface Global extends NodeJS.Global` if you want modify some but keep the rest of Global's properties, like `fetch` – casey Jun 01 '18 at 19:38
21

This is the right solution, not using Typescript's namespaces. It is also compatible with all eslint default rules:

// Declare a type.
interface CustomNodeJsGlobal extends NodeJS.Global {
    myExtraGlobalVariable: number;
    // You can declare anything you need.
}

Use it:

// Tell Typescript to use this type on the globally scoped `global` variable.
declare const global: CustomNodeJsGlobal;

function doSomething() {
  // Use it freely
  global.myExtraGlobalVariable = 5;
}

doSomething();
José Cabo
  • 6,149
  • 3
  • 28
  • 39
  • With the newest recommended @typescript-eslint rules, `eslint: error @typescript-eslint/no-unsafe-member-access - Unsafe member access .Global on an any value.` may pop up. Solved by adding `type NJSGlobal = NodeJS.Global;` previously and extending `NJSGlobal`. Beyond that, this solution seems the best way to go. – Zwei May 23 '20 at 00:23
  • 1
    Cleanest answer. People should be upvoting this one. – devuxer Mar 06 '21 at 20:22
10

Avoid typecasting any, it removes the purpose of typings. Instead install the type definitions needed (e.g. yarn add --dev @types/jsdom @types/node) and import to use:

import { DOMWindow, JSDOM } from 'jsdom'

interface Global extends NodeJS.Global {
  window: DOMWindow,
  document: Document,
  navigator: {
    userAgent: string
  }
}

const globalNode: Global = {
  window: window,
  document: window.document,
  navigator: {
    userAgent: 'node.js',
  },
  ...global
}
Will Squire
  • 6,127
  • 7
  • 45
  • 57
4

How to declare something in global

  1. Create global declaration file, for example: src/types/index.d.ts
  2. Add your global declaration file to tsconfig.json:
{
  "compilerOptions": {
    /* ... */
    "typeRoots": [
      "./src/types",
    ],
    /* ... */
  },
  /* ... */
}

From Typescript Documentation

You can add declarations to the global scope from inside a module

So, for example, if you want to add something extra to the array interface, you need to do:

observable.ts (example)

// observable.ts
export class Observable<T> {
  // ... still no implementation ...
}

src/types/index.d.ts

declare global {
  interface Array<T> {
    toObservable(): Observable<T>;
  }
}

someWhereInApp.ts

Array.prototype.toObservable = function () {
  // ...
};

Declaring variables to access them from global

If you want to add a global variable to use as global.myVariable you need to do:

src/types/index.d.ts

/* eslint-disable no-var */
declare global {
/*  ↓↓↓ "var" is important  */  
    var myVariable: string[];
}
/* eslint-enable no-var */

someWhereInApp.ts

const { myVariable } = global
Garvae
  • 465
  • 4
  • 14
3
declare namespace NodeJS {
  export interface Global { window: any;
  }
}
  • 25
    Code-only answers do very little to educate SO readers. Please edit your answer to include some explanation. Your answer is in the moderation queue because it has been flagged as low-quality. – mickmackusa Apr 19 '17 at 12:39
  • Tried your answer but global.window is still unresolved – Mick Jun 18 '18 at 10:30
0

A simple method can be use extend the Typescript "Window" type

step 1: append Window object

interface Window {
    foo:string // any type of your foo property
}

step 2: declare

let foo = 'value of foo'

step 3: add to window object

window.foo 

or

window.foo = foo

work for me...

Ali Virk
  • 49
  • 2
0

I was creating a nodeJS application with typescript and what fixed it for me was updating the @types/node in devDependencies to match the version I was using.