74

There is a simple TS package that is used as CommonJS modules and has no exports. TS files are compiled to JS files with the same name and used as require('package/option-foo').

tsconfig.json:

{
  "compilerOptions": {
    "target": "es5"
  }
}

option-foo.ts:

declare const GlobalVar: any;

function baz() {}

if (GlobalVar.foo) GlobalVar.baz = baz;

option-bar.ts:

declare const GlobalVar: any;

function baz() {}

if (GlobalVar.bar) GlobalVar.baz = baz;

The important part here is that option-foo and option-bar are never used together. There are other complimentary TS files in the project, but they don't affect anything, just needed to be transpiled to JS in one tsc run.

When tsc runs, it throws

Cannot redeclare block-scoped variable 'GlobalVar'.

Duplicate function implementation.

Cannot redeclare block-scoped variable 'GlobalVar'.

Duplicate function implementation.

for GlobalVar and baz in both files.

How can this be treated without complicating build process or the output from these two TS files?

Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • 1
    In this specific case, you can have something like a `custom-typings.d.ts` file and just declare it there, if they're both `any`. You could also try changing the desired `--module` compilation (try AMD/System, as I believe they scope files individually). – Seiyria Jan 30 '17 at 18:44

3 Answers3

185

TL;DR Just write export {} in the outermost scope of your files.


At some point there needs to be a semantic disambiguation for whether a file should be treated as a module (and have its own scope) or a script (and share the global scope with other scripts).

In the browser, this is easy - you should be able to use a <script type="module"> tag and you'll be able to use modules.

But what about any other place that utilizes JavaScript? Unfortunately there isn't a standard way at this point to make that distinction.

The way that TypeScript decided to tackle the problem was to simply state that a module is any file which contains an import or export.

So if your file doesn't have any sort of top-level import or export statements, then you'll occasionally see issues with global declarations interfering with each other.

To get around this, you can simple have an export statement that exports nothing. In other words, just write

export {};

somewhere at the top-level of your file.

Daniel Rosenwasser
  • 21,855
  • 13
  • 48
  • 61
  • On second thought, I ended with this solution, it just works. Thanks. – Estus Flask Feb 04 '17 at 22:07
  • 3
    This is the best explain about this problem. This problem confuse me a long while. Today, I finally find the reason. – Lin Du Jun 23 '17 at 03:40
  • so it's the TS compiler that is complaining, not a runtime issue, annoying. Is there a way to ignore TS errors like this? – Alexander Mills Oct 22 '17 at 04:31
  • You shouldn't try to ignore the errors in this case, especially if the errors come from `.ts` files, because those *will* actually experience a conflict. – Daniel Rosenwasser Oct 23 '17 at 14:44
  • Saved my time, this is the exact solution I was looking for. It's was annoying to see lots of warnings just because of this. – Ashish Singh Dec 10 '17 at 14:27
  • @DanielRosenwasser Couldn't it be a compiler setting? I thought that maybe moduleResolution or isolatedModules but no luck. I am working on forcing `truffle suite` to work with TS and it's kinda of a pain. – Krzysztof Kaczor Aug 30 '18 at 22:14
  • 3
    It works until you end up with SyntaxError: Unexpected token export. I wish they'd just accept that this is a bug in typescript for NodeJS and develop a fix for it. – m12lrpv Jun 16 '20 at 09:48
  • 3
    While I appreciate this hack, adding this is a smell. Why does TS consider an import a re-declaration? Modules are scoped. TS should really fix this bug.. – GN. Sep 23 '20 at 18:58
  • 1
    I have a legacy code, which has this problem. Adding export {} is not an option unfortunately. – Viktor M Mar 31 '21 at 21:17
20

GlobalVar and the functions are exposed to the global namespace, and TypeScript is warning you that the variable and the method name are re-declared. Because the two function are declared in the global namespace, you only need to declare them one time.

If you want to do this, use namespaces.

namespace foo {    
    declare const GlobalVar: any;
    function baz() {}
}

namespace bar {
    declare const GlobalVar: any;
    function baz() {}
}

You can call the functions in the same way as you call them in C#, by using the namespace name: bar.baz or foo.baz.

freedeveloper
  • 3,670
  • 34
  • 39
17

As of TypeScript 4.7, this is solved via "moduleDetection": "force" in your tsconfig.json:

{
  "compilerOptions": {
    // ... Various options here, such as module ...
    "moduleDetection": "force"
  }
}   

Reference: https://www.typescriptlang.org/tsconfig#moduleDetection

Original Github issue which drove this config: https://github.com/microsoft/TypeScript/issues/14279

lance.dolan
  • 3,493
  • 27
  • 36