2

I've seen hacks to make the error message go away.

But my question is why does TS do this?

What is the rationale, and purpose for this? I would expect a language would expect a util library to be imported in multiple files, thus would not consider it a 're-declaration'.

const uuid = require('uuid/v4');

Another file does the same import.

Cannot redeclare block-scoped variable 'uuid'

I can have a const foo = '123' in Module A, and re-declare a const foo = '123' in Module B with no issue.

Because modules are scoped.

Why does import / require behave differently?

What code makes this happen?

// utils.ts
const uuid = require('uuid/v4');
module.exports = function Utils() { ....}
.......
// import-helper.ts
const uuid = require('uuid/v4');
module.exports = function Helper() { ....}
.......

That's it, just two files with identical imports.

Here is the TSConfig

{
  "compilerOptions": {
    // * ===================================================================== *
    // * Basic Options
    // * ===================================================================== *
    "allowJs": true,
    "checkJs": true,
    "target": "ES2020",
    "noEmit": true,
    "pretty": true,
    "noErrorTruncation": true,
    "skipLibCheck": true,
    "jsx": "react",

    // * ===================================================================== *
    // * Strict Type-Checking Options
    // * ===================================================================== *
    "noImplicitAny": false, // disabling for now while converting the existing js code, too many errors otherwise
    // "strictNullChecks": false,
    // "noImplicitThis": false,

    // * ===================================================================== *
    // * Additional Checks
    // * ===================================================================== *
    "noUnusedLocals": true,
    "noUnusedParameters": true,

    // * ===================================================================== *
    // * Module Resolution Options
    // * ===================================================================== *
    "module": "commonjs",
    // "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "~actions/*": ["client/javascripts/state/actions/*"],
      "~components/*": ["client/javascripts/components/*"],
      "~constants/*": ["constants/*"],
      "~server/*": ["server/*"],
      "~client/*": ["client/*"],
      "~shared/*": ["shared/*"],
      "~state/*": ["client/javascripts/state/*"],
      "~utils/*": ["client/javascripts/utils/*"],
    },
    "esModuleInterop": true,
    "resolveJsonModule": true,

    // * ===================================================================== *
    // * Source Map Options
    // * ===================================================================== *
    "sourceMap": true,

    // * ===================================================================== *
    // * Experimental Options
    // * ===================================================================== *
    "experimentalDecorators": true,
  },

  "compileOnSave": false,

  "include": [
    "typings/**/*.d.ts",
    "./client/javascripts/**/*.js",
    "./client/javascripts/**/*.jsx",
    "./client/javascripts/**/*.ts",
    "./client/javascripts/**/*.tsx",
    "./constants/**/*.js",
    "./constants/**/*.ts",
    "./constants/**/*.json",
    "./server/**/*.js",
    "./server/**/*.ts",
    "./shared/**/*.js",
    "./shared/**/*.ts",
  ],

  "exclude": [
    "build",
    "coverage",
    "cypress",
    "dist",
    "locales",
    "node_modules",
    "tests",
  ],
}

GN.
  • 8,672
  • 10
  • 61
  • 126
  • You're misundestanding the error message. It's that you've *imported* a library multiple times. As it actually says, you're trying to re-declare a `let` or `const` variable with the same name. E.g., `const uuid = 1; const uuid = 2;`. See the error message - it should say where the redeclaration is happening - you have another variable in the same scope that shares the same name. – VLAZ Sep 23 '20 at 18:55
  • Yes but modules are scoped. You can have a `const foo =123` in module One, and a `const foo =123` in module Two. Checking for re-declarations across modules makes no sense. – GN. Sep 23 '20 at 19:01
  • 1
    I'd like to see a [mcve] of this issue because something is REALLY wrong if that's happening. My assumption is that you have copy/pasted some line by accident. – VLAZ Sep 23 '20 at 19:02
  • @GN. `Yes but modules are scoped.` are you sure you've created a module? The line of code you've showed us uses `require`, not `import` – Nicholas Tower Sep 23 '20 at 19:04
  • I've updated with better examples. Yes, require not import. Same required module in two files. The file only required it once.. – GN. Sep 23 '20 at 19:07
  • A file is a module if it uses `import` and/or `export`. You're not using import, so are you using `export`? I don't mean `module.exports` – Nicholas Tower Sep 23 '20 at 19:08
  • I am, just updated question. Both files, which require() the UUID lib are modules themselves. They are exported as modules. – GN. Sep 23 '20 at 19:10
  • 1
    No, they are not. As stated in the comments, a file is a module, **IFF and only IF** it contains *at least* either an `import` or an `export` declaration. To quote from the comment that you replied to: "I don't mean `module.exports`". – Jörg W Mittag Sep 23 '20 at 19:13
  • Since when is a `module.exports` is not a module? – GN. Sep 23 '20 at 19:19
  • Variables declared within a module (yes, including `module.exports`) are not exposed to a global scope. That is, UNLESS they are explicitly exported . Aaand, I’m not exporting UUID. I’m exporting `function util` which uses UUID. So again, it does not make sense why TS considers the "require-ing" of UUID a re-declaration. – GN. Sep 23 '20 at 19:24
  • As was already explained multiple times, it is *not* a module. A file is a module **IFF AND ONLY IFF** it contains either at least one `import` or at least one `export`. Not a `require`, an `import`. Not a `module.exports`, an `export`. – Jörg W Mittag Sep 23 '20 at 19:24
  • Please link me to some evidence that a `module.exports` is not a module. And by the way, Babel is mostly converting import / export statements to require / module.exports under the hood. – GN. Sep 23 '20 at 19:26
  • The ECMAScript specification, the TypeScript specification, the TypeScript handbook, every TypeScript tutorial ever written, every ECMAScript Modules tutorial ever written, dozens of questions on [so], and multiple comments by multiple people on this very question. Oh, and also your TypeScript compiler because otherwise you wouldn't ask this question. – Jörg W Mittag Sep 23 '20 at 19:28
  • @GN. I share your problem. CommonJS modules _are_ wrapped with a function when they are required so your point is valid. I don't know how to solve since (in my case) adding a hacky "export {}" makes electron throw an error. – alpersunter Nov 19 '21 at 00:47

3 Answers3

0

When TypeScript doesn't see an import or export, it considers the file to be a script, not a module. This is solved via "moduleDetection": "force" in your tsconfig.json:

{
  "compilerOptions": {
    "moduleDetection": "force"
  }
}   

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

Original anwser: https://stackoverflow.com/a/74968079/10538886

倪俊杰
  • 56
  • 3
-1

Note that you can always put the require statement inside the modules.export instead of in the global context at the top of the file.

omarjebari
  • 4,861
  • 3
  • 34
  • 32
-2

TypeScript is intended to be a superset of ECMAScript / JavaScript. ECMAScript does not allow block-scoped variables (const or let) to be re-declared, therefore TypeScript also does not allow it.

Actually, this is true for most programming languages that have explicitly-declared block-scoped variables: C, C++, Java, C#, you name it.

C# even goes a step further: you cannot even shadow a block-scoped variable in a nested block.

I cannot reproduce your problem when using two separate modules:

// utils.ts
import uuid from 'uuid/v4';
export function Utils() { /* … */ }
// import-helper.ts
import uuid from 'uuid/v4';
export function Helper() { /* … */ }

I can reproduce it when using scripts instead of modules, since scripts do not have separate scopes:

// utils.ts
const uuid = require('uuid/v4');
module.exports = function Utils() { /* … */ }
// import-helper.ts
const uuid = require('uuid/v4');
module.exports = function Helper() { /* … */ }

What makes a module a module is explained in the TypeScript Handbook's chapter on Modules (bold emphasis mine):

In TypeScript, just as in ECMAScript 2015, any file containing a top-level import or export is considered a module. Conversely, a file without any top-level import or export declarations is treated as a script whose contents are available in the global scope (and therefore to modules as well).

ECMAScript, although not relevant here, is similar. Unfortunately, ECMA-262 does not state the difference as clearly as the TypeScript Handbook does, you have to dig through the chapter on Modules and map out all the possible legal and illegal syntaxes to figure out the difference. But since the error here is a TypeScript error, ECMAScript is not really relevant anyway.

Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
  • Yes but modules are scoped. You can have a `const foo =123` in module One, and a `const foo =123` in module Two. Checking for re-declarations across modules makes no sense. – GN. Sep 23 '20 at 19:00
  • Of course. But that has nothing to do with this. We are talking about re-declaring a variable *in the same block*. – Jörg W Mittag Sep 23 '20 at 19:01
  • I'm not doing that though. I have only ONE `const uuid = require('uuid/v4')` per module / file. – GN. Sep 23 '20 at 19:06
  • "I have only ONE `const uuid = require('uuid/v4')` per module / file." – Which is it? Module or file? Those are not the same thing. – Jörg W Mittag Sep 23 '20 at 19:26
  • Lol. The words `const uuid = require('uuid/v4')` appear merely once per file. – GN. Sep 23 '20 at 19:29
  • Then the problem is obvious: scripts don't have separate scopes, only modules do, so you are *indeed* redeclaring `const uuid` in the same scope. – Jörg W Mittag Sep 23 '20 at 19:30
  • Please read the spec here: CommonJS modules are modules. https://nodejs.org/api/modules.html#modules_modules_commonjs_modules – GN. Sep 23 '20 at 19:33
  • What does CommonJS have to do with a TypeScript error? – Jörg W Mittag Sep 23 '20 at 19:34
  • Haven't you been making the case that CommonJS modules are not modules? Only ES6 import/exports are, for last N comments? ‍♂️ – GN. Sep 23 '20 at 19:36
  • I haven't mentioned CommonJS a single time before my last comment. You are asking why your **TypeScript Code** is not working, and the reason is that your **TypeScript Code** is not a **TypeScript Module** according to the rules of the **TypeScript Programming Language**. Any other module system except TypeScript Modules is irrelevant. Any other programming language except TypeScript is irrelevant. It may very well be that there is some language somewhere in which your code might be a module, but it is not a module in TypeScript, which is the language that the TypeScript compiler accepts. – Jörg W Mittag Sep 23 '20 at 19:42
  • `module.exports` is an CommonJS standard. You DID mention that. I think you mean Typescript Module, I mean CommonJS module. – GN. Sep 23 '20 at 20:32