195

In JavaScript I can just do this:

 something = 'testing';

And then in another file:

 if (something === 'testing')

and it will have something be defined (as long as they were called in the correct order).

I can't seem to figure out how to do that in TypeScript.

This is what I have tried.

In a .d.ts file:

interface Window { something: string; }

Then in my main.ts file:

 window.something = 'testing';

then in another file:

 if (window.something  === 'testing')

And this works. But I want to be able to lose the window. part of it and just have my something be global. Is there a way to do that in TypeScript?

(In case someone is interested, I am really trying to setup my logging for my application. I want to be able to call log.Debug from any file without having to import and create objects.)

wonea
  • 4,783
  • 17
  • 86
  • 139
Vaccano
  • 78,325
  • 149
  • 468
  • 850
  • 3
    Alternatively: Don't create globals. Importing is _really easy_ with vscode. Just type the thing you want to use, hit tab to auto-import, and continue on. – Devin Rhode Dec 30 '20 at 05:04
  • 2
    @DevinRhode Ideally that's the way to go. But sometimes you need to use a JavaScript library that declares global variables and want to use a typed version of it in your project. – Daniel Kaplan Aug 05 '23 at 22:50
  • It's true, actually I had a use for globals recently, and created a new approach, see: https://stackoverflow.com/a/76844373/565877 – Devin Rhode Aug 07 '23 at 16:49

19 Answers19

129

globalThis is the future.

First, TypeScript files have two kinds of scopes

global scope

If your file hasn't any import or export line, this file would be executed in global scope that all declaration in it are visible outside this file.

So we would create global variables like this:

// xx.d.ts
declare var age: number

// or 
// xx.ts
// with or without declare keyword
var age: number

// other.ts
globalThis.age = 18 // no error

All magic come from var. Replace var with let or const won't work.

module scope

If your file has any import or export line, this file would be executed within its own scope that we need to extend global by declaration-merging.

// xx[.d].ts
declare global {
  var age: number;
}

// other.ts
globalThis.age = 18 // no error

You can see more about module in official docs

edvard chen
  • 2,320
  • 1
  • 12
  • 10
  • 1
    But how would you do this without the 'var' hack? I guess that means, how would I do type augmentation on globalThis? – Tom Jul 11 '19 at 15:10
  • 1
    @Tom `var` is necessary. But you can just declare variable without initialization – edvard chen Jul 12 '19 at 02:56
  • 2
    Worked for me (at least for now) thanks so much, I wish this answer would move up. FYI: In my case I needed to add `// @ts-ignore` above the `globalThis` line for the linter. – David Feb 29 '20 at 06:58
  • How would you call the age variable in to any typescript file? – Ashok kumar Ganesan Sep 24 '20 at 07:20
  • Even when this solution may work, the compiler is still complaining: 'Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature.', and I don't think adding `// @ts-ignore` commend is a good practice – Anthony Luzquiños Oct 18 '21 at 23:45
  • hi @AnthonyLuzquiños, I cannot reproduce the "complain" with TypeScript 4.4 – edvard chen Oct 30 '21 at 13:09
  • Hi @edvardchen, I have created a [project](https://github.com/AnthonyLzq/test-global) in case you want to give it a try. – Anthony Luzquiños Nov 25 '21 at 23:27
  • 1
    @edvardchen, I could finally solve the issue, it was related to ts-node, this is a related [question](https://stackoverflow.com/questions/51610583/ts-node-ignores-d-ts-files-while-tsc-successfully-compiles-the-project) to that, I have also wrote a Medium article about it, [here](https://medium.com/@anthonylzq/how-to-create-global-variables-in-typescript-with-node-js-9ca24f648991) is it. – Anthony Luzquiños Dec 12 '21 at 21:03
  • Is this only a declaration or also defining the variable? Can you initialize it in the global .ts file ? – gwhiz Sep 24 '22 at 07:55
  • 1
    @gwhiz only a declaration. You should always use globalThis to do the actual assignment. `var age = 123` won't work. – edvard chen Sep 25 '22 at 16:20
  • It's 2023 and this still works. – Daniel Kaplan Aug 07 '23 at 21:13
  • Doesn't TS just compile lets and consts down to a var anyway? I don't understand why this works. – Joe Johnston Aug 08 '23 at 21:57
  • 1
    @JoeJohnston I think that depends on your tsconfig `target`. Most modern targets use let and const. – Daniel Kaplan Sep 01 '23 at 00:28
83

Inside a .d.ts definition file

type MyGlobalFunctionType = (name: string) => void

If you work in the browser, you add members to the browser's window context:

interface Window {
  myGlobalFunction: MyGlobalFunctionType
}

Same idea for NodeJS:

declare module NodeJS {
  interface Global {
    myGlobalFunction: MyGlobalFunctionType
  }
}

Now you declare the root variable (that will actually live on window or global)

declare const myGlobalFunction: MyGlobalFunctionType;

Then in a regular .ts file, but imported as side-effect, you actually implement it:

global/* or window */.myGlobalFunction = function (name: string) {
  console.log("Hey !", name);
};

And finally use it elsewhere in the codebase, with either:

global/* or window */.myGlobalFunction("Kevin");

myGlobalFunction("Kevin");
Benoit B.
  • 3,561
  • 24
  • 19
  • 12
    ( Just making a passing comment ) Talk about a lot of work to create a simple global variable! lol – Pytth Jan 31 '18 at 19:30
  • 9
    @Benoit I don't get the part that says "but imported as side-effect". What do you mean by that?. I tried doing this, but it's not working for me, could you share a source code example? – BernalCarlos Mar 10 '18 at 23:17
  • 2
    This works fine for `.ts` files but when I use `window.myGlobalFunction` in my `.tsx` files, it doesn't. What else do I need to change?! – Kousha Aug 30 '18 at 05:49
  • 28
    Where do you put the d.ts file, and how do you configure TypeScript to load it? – Matthias Jan 12 '19 at 06:05
  • 8
    This is ridiculously complicated. – Szczepan Hołyszewski Aug 17 '21 at 17:51
  • Be aware that you have to name `*.d.ts` files without extra dots, i.e. `some.global.d.ts` file could not be recognized properly, but `some-global.d.ts` could (with a dash instead of the first dot). – Ruslan Zhomir Sep 02 '21 at 10:12
  • 1
    typescript global object key's have any type...anyone knows why? – Rupak Jan 25 '22 at 12:47
  • Note: the Node code is outdated now (since Node 16 apparently). Instead just `declare global { var whatever }` – Robert Sandiford Aug 05 '23 at 22:34
36

This is how I have fixed it:

Steps:

  1. Declared a global namespace, for e.g. custom.d.ts as below :
declare global {
    namespace NodeJS {
        interface Global {
            Config: {}
        }
    }
}
export default global;
  1. Map the above created a file into "tsconfig.json" as below:
"typeRoots": ["src/types/custom.d.ts" ]
  1. Get the above created global variable in any of the files as below:
console.log(global.config)

Note:

  1. typescript version: "3.0.1".

  2. In my case, the requirement was to set the global variable before boots up the application and the variable should access throughout the dependent objects so that we can get the required config properties.

Hope this helps!

Thank you

Ozgur Ozcitak
  • 10,409
  • 8
  • 46
  • 56
Vikash Choudhary
  • 1,439
  • 12
  • 9
21

The text posted here is a short version of the article TypeScript and Global Variables in Node.js

Since the release of TypeScript 3.4 there's a documented way to do it.

Create a file in the root of the project named global.d.ts with the following content. Please note:

  • The use of var , it’s required for this to work (see typescriptlang.org for information about this).
  • Without the export {}, all variables will become any
declare global {
    var Config: {
        Foo: string;
    };
    var Foo: string;
}
export { };

Make sure the tsconfig.json has proper sections for include and exclude. Example follows:

"include": [
    "src/**/*.ts",
  ],
  "exclude": [
    "node_modules",
    "<node_internals>/**",
    "bin/**"
  ]

To use the variables, just do something like:

import * as Logger from './logger';

// Initialize early
global.log = Logger;

// Use it
log.Info("Booting system...");

Enjoy :)

Tomas Nilsson
  • 228
  • 2
  • 4
  • 1
    Finally.. `export { };` was the key. In `.ts` you can use `global.log` or `globalThis.log` interchangeably. – Teneko Sep 10 '21 at 12:49
18

I found a way that works if I use JavaScript combined with TypeScript.

logging.d.ts:

declare var log: log4javascript.Logger;

log-declaration.js:

log = null;

initalize-app.ts

import './log-declaration.js';

// Call stuff to actually setup log.  
// Similar to this:
log = functionToSetupLog();

This puts it in the global scope and TypeScript knows about it. So I can use it in all my files.

NOTE: I think this only works because I have the allowJs TypeScript option set to true.

If someone posts an pure TypeScript solution, I will accept that.

wonea
  • 4,783
  • 17
  • 86
  • 139
Vaccano
  • 78,325
  • 149
  • 468
  • 850
  • 1
    In your initalize-app.ts file you can use: `declare var log: any;` Then you don't need to have the d.ts file or the js file. You could replace any with an actual type or an interface definition as well. – mattferderer Feb 07 '17 at 18:18
6

im using only this

import {globalVar} from "./globals";
declare let window:any;
window.globalVar = globalVar;
Dima V
  • 129
  • 1
  • 3
  • 6
    This throws away all type information. May as well use Javascript. – Timmmm Nov 12 '19 at 10:46
  • This solution makes sense when you are making frontend completely outside from backend (for different purposes), like different teams or due to security reasons. And then backend inject something inside window. I honestly prefer specify EACH variable inside components in this case. So I believer this solution might have a life :) Do not think this is wide case, but I faced with it one day.... – Kirill Husiatyn Jan 12 '21 at 10:05
6

I spent couple hours to figure out proper way to do it. In my case I'm trying to define global "log" variable so the steps were:

1) configure your tsconfig.json to include your defined types (src/types folder, node_modules - is up to you):

...other stuff...
"paths": {
  "*": ["node_modules/*", "src/types/*"]
}

2) create file src/types/global.d.ts with following content (no imports! - this is important), feel free to change any to match your needs + use window interface instead of NodeJS if you are working with browser:

/**
 * IMPORTANT - do not use imports in this file!
 * It will break global definition.
 */
declare namespace NodeJS {
    export interface Global {
        log: any;
    }
}

declare var log: any;

3) now you can finally use/implement log where its needed:

// in one file
global.log = someCoolLogger();
// in another file
log.info('hello world');
// or if its a variable
global.log = 'INFO'
Alexey
  • 1,914
  • 16
  • 13
  • What's the `paths` in `tsconfig.json`? The docs don't have any mention of it. – tambre Sep 03 '19 at 12:33
  • And why is `Global` capitalized in the definitions, but not in actual usage? – tambre Sep 03 '19 at 12:51
  • 1
    @tambre not sure why TS docs don't have it documented, you can find some details regarding this config here: http://json.schemastore.org/tsconfig and here: https://basarat.gitbooks.io/typescript/docs/project/tsconfig.html Regarding `Global` with capital - this is how "global" interface declaration named in nodejs namespace. – Alexey Sep 04 '19 at 20:16
5

Extend the other answer about globalThis (see MDN and TypeScript 3.4 note) with more specific examples (TypeScript only without mixing with JavaScript), as the behavior was fairly confusing. All examples are run under Nodejs v12.14.1 and TypeScript Version 4.2.3.

Simplest case with global scope

declare var ENVIRONMENT: string;
globalThis.ENVIRONMENT = 'PROD';
console.log(ENVIRONMENT);
console.log(globalThis.ENVIRONMENT);
// output
// PROD
// PROD

This file doesn't import or export so it's a global scope file. You can compile the above TypeScript code without any error. Note that you have to use var. Using let will throw error TS2339: Property 'ENVIRONMENT' does not exist on type 'typeof globalThis'.

You might notice that we declared the variable as opposed to the following which also works.

var ENVIRONMENT: string;
ENVIRONMENT = 'DEV';
globalThis.ENVIRONMENT = 'PROD';
console.log(ENVIRONMENT);
console.log(globalThis.ENVIRONMENT);
// output
// DEV
// PROD

The output is from Nodejs v12.14.1. I also tested it in Chrome (after compiling to JS) and both output PROD. So I'd suggest using globalThis all the time.

Simple case with module scope

declare var ENVIRONMENT: string;
globalThis.ENVIRONMENT = 'PROD';
export {};

Once we add export statement, it becomes a module scope file, which throws error TS7017: Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature. The solution is to augment global scope.

declare global {
  var ENVIRONMENT: string;
}
globalThis.ENVIRONMENT = 'PROD';
console.log(globalThis.ENVIRONMENT);
export {};

You still have to use var, otherwise you will get error TS2339: Property 'ENVIRONMENT' does not exist on type 'typeof globalThis'..

Import for side effect

// ./main.ts
import './environment_prod';

console.log(ENVIRONMENT);
console.log(globalThis.ENVIRONMENT);
// ./environment_prod.ts
declare var ENVIRONMENT: string;
globalThis.ENVIRONMENT = 'PROD';

Or

// ./environment_prod.ts
declare global {
  var ENVIRONMENT: string;
}
globalThis.ENVIRONMENT = 'PROD';
export {}; // Makes the current file a module.

Browserify two files

Suppose both main.ts and environment_prod.ts are entry files. Browserify will wrap them (after compiled to JS) into local scoped functions which necessitates the use of globalThis.

// ./main.ts
declare var ENVIRONMENT: string;
console.log(ENVIRONMENT);
console.log(globalThis.ENVIRONMENT);

// ./environment_prod.ts
declare var ENVIRONMENT: string;
globalThis.ENVIRONMENT = 'PROD';

But it's more type-safe to share a declaration file which can then be imported by both entry files, to avoid typos of variable names or type names.

// ./main.ts
import './environment';

console.log(ENVIRONMENT);
console.log(globalThis.ENVIRONMENT);

// ./environment_prod.ts
import './environment';

globalThis.ENVIRONMENT = 'PROD';

// ./environment.ts
type Environment = 'PROD' | 'DEV' | 'LOCAL';

declare var ENVIRONMENT: Environment;

Note that the order matters: browserify environment_prod.js main.js > bin.js

Yuxuan Xie
  • 61
  • 2
  • 3
4

It is work on browser
I found this in https://stackoverflow.com/a/12709880/15859431

declare global {
    interface Window {
        myGlobalFunction: myGlobalFunction
    }
}
Xiao_e_yun
  • 188
  • 1
  • 10
3

Okay, so this is probably even uglier that what you did, but anyway...

but I do the same so...

What you can do to do it in pure TypeScript, is to use the eval function like so :

declare var something: string;
eval("something = 'testing';")

And later you'll be able to do

if (something === 'testing')

This is nothing more than a hack to force executing the instruction without TypeScript refusing to compile, and we declare var for TypeScript to compile the rest of the code.

wonea
  • 4,783
  • 17
  • 86
  • 139
tforgione
  • 1,234
  • 15
  • 29
  • This isn't working for me. I have eval('myglobalvar = {};');. It results in: Uncaught ReferenceError: myglobalvar is not defined at eval (eval at ..... ). Funnily though if I run it from the console then it works. – Ryan How Jun 12 '17 at 13:50
  • @RyanHow what browser do you use ? I tried it on mine and I can't reproduce your error – tforgione Jun 12 '17 at 18:24
  • 2
    @DragonRock I tried Chrome and FF. So I'm really not sure what was going on. Anyway ended up doing declare var myglobalvar; (window).myglobalvar= {}; Then I can reference it without window afterwards. – Ryan How Jun 14 '17 at 01:11
  • 3
    This really should be done with a `.d.ts` definition file instead. – brianespinosa Jul 27 '18 at 19:54
  • 2
    This works for me: `export declare const SERVER = "10.1.1.26";` – xinthose Oct 22 '18 at 14:55
  • 5
    Using `eval` is highly discouraged that's why I suggest using this solution https://stackoverflow.com/a/56984504/5506278 – Yaroslav Bai Nov 29 '19 at 12:59
  • 1
    Using `eval` is dirty, consider using `Object.assign(globalThis, {something: 'something'})` instead – Dimava May 12 '21 at 15:54
3

I needed to make lodash global to use an existing .js file that I could not change, only require.

I found that this worked:

import * as lodash from 'lodash';

(global as any)._ = lodash;
TrueWill
  • 25,132
  • 10
  • 101
  • 150
3

Also check out the answer here

// global.d.ts
export const thisIsAModule = true; // <-- definitely in a module

declare global {
    var foo: string;
}
francis
  • 3,852
  • 1
  • 28
  • 30
2

This is working for me, as described in this thread:

declare let something: string;
something = 'foo';
Nacho Coloma
  • 7,070
  • 2
  • 40
  • 43
1

If anyone uses Deno:

// global.ts
interface Global {
    some_number: number;
}

export const global = {
    some_number: 1,
} as Global;

Then every script that imports global can modify its values (but not global itself!) like this:

// test.ts
import { global } from "./global.ts";
export function test() {
    global.someNumber++;
}

You can prove that it works by running the function from another file:

// main.ts
import { global } from "./global.ts";
import { test } from "./test.ts";

console.log(global);
test();
console.log(global);

… will print:

{ someNumber: 1 }
{ someNumber: 2 }
MrKleeblatt
  • 349
  • 2
  • 8
  • This is better than most answers. You avoided creating `.d.ts`, and avoided touching `tsconfig.json`! But there's a funny thing in the code here: If we are already importing ane exporting, we can drop `interface Global { ... }` and `} as Global;` - just have a shared mutable `global` `Object` that you import/export and mutate as needed. If people can do this, given their constraints, it's definitely better than other approaches. – Devin Rhode Aug 06 '23 at 03:40
0

As an addon to Dima V's answer this is what I did to make this work for me.

// First declare the window global outside the class

declare let window: any;

// Inside the required class method

let globVarName = window.globVarName;

Jonathan Cardoz
  • 874
  • 9
  • 12
  • 1
    Can the person who down-voted this explain the reason? – Jonathan Cardoz Dec 20 '18 at 05:12
  • 10
    Well, this is a solution like putting a piece of black tape over your "check engine" light on your dashboard. It fixes the problem but not in the right way. It obliterates types. It's not an optimal solution; it's a workaround that also works around the main purpose of typescript: to have types. – Matthias Jan 12 '19 at 06:09
0

This is how you create global variables for node app and typescript

File name is called typings/index.ts

declare global {
  var countSkipped: number;
  var countProcessed: number;
  var countLibUsedByFile: Record<string, number>;
}

export {};

If you happen to overrides a few prototypes, here's how you can add the typescript definition for the string prototype

declare global {
  interface String {
    blue(): string;
    yellow(): string;
    green(): string;
    red(): string;
  }
}

export {};

This is the sample prototype for the above string

String.prototype.blue = function () {
  return `\x1b[36m${this}\x1b[0m`;
};

String.prototype.yellow = function () {
  return `\x1b[33m${this}\x1b[0m`;
};

String.prototype.green = function () {
  return `\x1b[32m${this}\x1b[0m`;
};

String.prototype.red = function () {
  return `\x1b[31m${this}\x1b[0m`;
};
Sy Le
  • 79
  • 1
  • 2
0

Ok. Read through all the existing answers, and I think I owe some credit to https://stackoverflow.com/a/67040805/565877 (@Yuxuan Xie). Not sure where this idea originated from, myself or someone else.

Anyway, this is my current strategy:

Preconditions:

Let's evaluate the best strategy for you.

  1. If you can have a shared mutable "global" object that you control, and simply import/export where you need it, that's better than mucking around with globalThis or declare global etc.
  2. If you can "wrap" some other value with typescript, that's also great.
  • export const Object_keys_typed = Object.keys as SOME_TYPE

Lastly

If there's just some global being created outside of your typescript code, proceed.

Define the globals

  1. Create this file:
  2. define-globals-for-some-specific-thing.ts Add add your code like this:
// Importing constants is fine:
// import type { FOO } from './constants';

// Import this in order to access these globals.
declare global {

  // Because this is a `const/let`, typescript will complain if you access it via `globalThis.WAS_LOADED_VIA_INDEX_HTML`,
  // You instead need to remove the `globalThis.` prefix
  /** determine if this was loaded via index.html, or some other npm/script tag function */
  const WAS_LOADED_VIA_INDEX_HTML: boolean | undefined;

  // This can be accessed via `window/globalThis.CanBeReadOffWindow`
  var CanBeReadOffWindow: 'asdf' | undefined;
}

// Makes the current file a module, with a clear importable name
export const defineGlobals = undefined;

Use the globals:

Inside myFile.ts:

import './define-globals-for-specific-task.ts';

export const wasLoadedViaIndexHtml = () => {
  return WAS_LOADED_VIA_INDEX_HTML;
};

Why this is good

  1. We avoid touching tsconfig.json.
  2. We avoid creating .d.ts files.
  3. We're forced to use imports/exports where we want to access the globals.
  • This means if you scan import statements to get the "gist" of a file, you can see it will interact with some globals defined in define-globals-for-specific-task.
  • We can search for these import statements, and find everywhere we use a global

There's probably a better name template than define-globals-for-specific-task - perhaps define-SPECIFIC_TASK-global-types

Devin Rhode
  • 23,026
  • 8
  • 58
  • 72
-1

I'm using this:

interface Window {
    globalthing: any;
}

declare var globalthing: any;
YehudaR
  • 41
  • 6
-1

Firs declare your Global variable Like this

declare global {
  var options: {
    billingAddress: {
      country: string;
      state: string;
      city: string;
      postcode: string;
      street1: string;
      street2: string;
    };
    mandatoryBillingFields: {
      country: boolean;
      state: boolean;
      city: boolean;
      postcode: boolean;
      street1: boolean;
      street2: boolean;
    };
  };
}

In functions you can use Like this

const updateGlobalVariable = () => {
      window.options = {
        billingAddress: {
          country: 'US',
          state: 'NY',
          city: 'New York',
          postcode: '12345',
          street1: 'Suite 1234',
          street2: 'Some Road',
        },
        mandatoryBillingFields: {
          country: true,
          state: true,
          city: true,
          postcode: true,
          street1: true,
          street2: false,
        },
      };
    };
Shaheel_Azhar
  • 73
  • 2
  • 7
  • I was specifically asking for a way to remove the `window.` part of the referencing of the variable. (To reference it directly). This still uses `window.` to get at the variable. `window` is global here, not `options`. – Vaccano Nov 04 '22 at 20:37