Ways to inform TSC that script files won't have shared scope and should disregard redeclarations

Issue to Resolve

I am utilizing the TypeScript-powered JavaScript checking capabilities of VSCode to perform type-checking on multiple JS files. These files are intended to be imported via <script> tags in HTML and do not contain any export/import statements, making them true script files rather than module files.

Typically, with script files, redeclaring a block-scoped variable is not allowed. For example:

  • file_A.js: let myStr = 'hello';
  • file_B.js:
    let myStr = 'hello'; // <-- Error, Cannot redeclare block-scoped variable 'myStr'

However, these files have inherent separation due to their file structure:

 - jsconfig.json (or tsconfig.json)
 - dir_A/
     - index.html (includes file_A.js via script tag)
     - file_A.js
 - dir_B/
     - index.html (includes file_B.js via script tag)
     - file_B.js

Is there a straightforward way to avoid this TS error...

Cannot redeclare block-scoped variable 'myStr'.ts(2451)

... by informing VSCode / TSC that even though both file_A and file_B declare the same variable (let myStr = 'hello'), it is not a re-declaration because these files are never executed or imported in the same scope (no HTML file or script executes both file_A.js and file_B.js)?

Solutions that function but are cumbersome include:

  • Placing a config file in each sub-directory and removing the root config
  • Encapsulating the code in each JS file with an IIFE
  • Changing variable declarations to use var
  • Adding an empty export (export {}) to each .js file to force it to be treated as a module, then having a build step that removes that line when copying files into /dist

I am hopeful that there might be a solution involving ambient declaration files or configuration settings to instruct TS to treat these files as modules, even without import/export declarations.

I also understand that adding an empty export declaration (export {}) to each file to convert it into a module would require using

<script type="module">
in the consuming HTML, which may not be ideal for legacy browser support and automatic top-level scoped variables.

In Short

The core of my inquiry is: "Is there a simple method to direct TSC to consider script files as modules (since they will be consumed that way) despite lacking import/export/module syntax?"


Complete Example for Reproduction

File Structure:

.
 |-dir_A
 | |-file_A.js
 |-dir_B
 | |-file_B.js
 |-tsconfig.json

I omitted the index.html files outside of this structure since they do not impact TSC, and the error persists with or without them.

Both file_A.js and file_B.js contain identical code:

let myStr = 'hello';

I have experimented with various configurations, but the minimal working config is:

{
    "compilerOptions": {
        "checkJs": true,
        "allowJs": true,
        "noEmit": true,
    }
}

I also attempted this with

"isolatedModules": true
, but it did not resolve the issue.


UPDATE: Most Promising Solution Found

After additional research, I believe I discovered the closest thing to an "official" answer, though it remains unsatisfactory as no definitive solution currently exists.

This discussion thread, issue #18232, is essentially the primary conversation addressing the conflict between file scoping in scripts versus modules and how TS cannot predict precisely how a file will be utilized. The issue remains open and led me to uncover some other pertinent resources:

  • This issue/feature request (#27535) closely aligns with what I seek: a TSC flag/option allowing me to specify that script files should be treated as modules. It was closed due to overlapping discussions with the aforementioned thread.
  • This StackOverflow query offers one of the less-than-ideal solutions: opting for modules instead of scripts (which does not address the underlying issue).
  • TC39 Proposal - "Modules Pragma"
    • This proposal seems optimal; pragmas can be interpreted by both TSC and browsers without breaking compatibility with older interpreters/engines (unlike the empty export {} workaround, which necessitates module support)

Regrettably, activity on the aforementioned thread has waned over the past year.

Answer №1

An official solution has been released for this issue, eliminating the need for workarounds such as empty exports. TypeScript now includes a compiler option (in version 4.7) called moduleDetection, which changes how files are recognized as modules by default.

To resolve the problem described in the original question and have each .js file treated as a module without needing explicit imports or exports, ensure that the tsconfig.json file contains

"moduleDetection": "force"
, like this:

{
    "compilerOptions": {
        "checkJs": true,
        "allowJs": true,
        "noEmit": true,
        "moduleDetection": "force"
    }
}

For more information about this feature, consult:

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Disabling eqeqeq for create-react-app with TypeScript: A step-by-step guide

I've exhaustively attempted various methods, but I can't seem to figure out how to disable eqeqeq for my specific project. The framework of my project is based on create-react-app using TypeScript. Here are some visual references: https://i.ss ...

Understanding the NavigationContainer reference in Typescript and react-navigation

In my current project with react-navigation, I've come across a scenario where I need to navigate from outside of a component (specifically after receiving a push notification). The challenge is that when I use the navigation.navigate method from wit ...

Missing from the TypeScript compilation are Angular5's polyfills.ts and main.ts files

Here is the structure of my Angular5 project. https://i.stack.imgur.com/tmbE7.png Within both tsconfig.app.json and package.json, there is a common section: "include": [ "/src/main.ts", "/src/polyfills.ts" ] Despite trying various solu ...

Angular 10 introduces a new feature where BehaviorSubject can now hold and emit two

Currently, I am attempting to log in using Firebase. The login system is functioning correctly; however, I am facing a challenge in retrieving the error name from the authentication service and displaying it in my login component. SignIn(email: string, pas ...

Discovering ways to fetch an array of objects using object and arrays in JavaScript

When comparing an array of objects with a single object and listing the arrays in JavaScript, specific conditions need to be met to retrieve the array of objects: If the itemvalue and idvalue are the same, check if the arrobj cid has the same codevalue ...

Are there more efficient methods for utilizing local scope when defining a variable?

Having experience in the ML world, I'm used to creating variables with limited scope like this: let myVar = let result1 = doSomething() let result2 = doSomethingElse() result1 + result2 In TypeScript, it seems you can achieve similar sco ...

The context value is lagging behind and not updating promptly

context.tsx import { PropsWithChildren, createContext, useContext, useState } from "react"; import auth_svc from "../services/authServices"; import { Result, message } from "antd"; interface Result { _id: String | null; f ...

Unable to locate identifiers 'Let' (TS2304), 'headers' (TS2552), and 'options' in a TypeScript script

I am new to using Angular and Ionic. While following a tutorial, I encountered the following errors: Cannot find name ‘Let’ (TS2304) Cannot find name ‘headers’. Did you mean ‘Headers’? (TS2552) Cannot find name ‘options’. Did you mean ‘ ...

What is the best way to assign the selected attribute to my mat-option element?

I am encountering an issue with my mat-select and mat-option control. I am trying to apply the selected attribute to the first mat-option control without using binding on [(ngModel)] or [(value)]. The mat-option control is being generated by the *ngFor d ...

The marker.js 2 documentation states that integrating marker.js with React poses compatibility issues when using next.js

I have followed the documentation for using this in my next.js app, but unfortunately, it's not functioning as expected. There are no errors, but nothing happens when I click on the image. It appears that this library may not be compatible with Next. ...

Collaborating on code between a Typescript React application and a Typescript Express application

Struggling to find a smart way to share code between two interconnected projects? Look no further! I've got a React web app encompassing client code and an Express app serving as both the API and the React web app host. Since I use Typescript in both ...

Is there a way to resolve the issue of the argument being of type Boolean or undefined in React and TypeScript?

Encountering an issue, Received an error message stating: 'Argument of type 'boolean | undefined' is not assignable to parameter of 'type boolean'. Type 'undefined' is not assignable to type 'boolean'.' ...

Send the template to the enclosed grid column

I enclosed a kendo-grid-column within a new component by using <imx-gridColumn field="Count" title="Count"> ... </imx-gridColumn> The component that includes the imx-gridColumn is templated with <kendo-grid-column #column field="{{field}} ...

Angular FormControl is a built-in class that belongs to the Angular forms module. It

I’ve been working on adjusting tslint for the proper return type, and here’s what I have so far: get formControls(): any { return this.form.controls; } However, I encountered an error stating Type declaration of 'any' loses type-safet ...

Can the garbage collector in Typescript/JavaScript effectively handle circular references, or does it result in memory leaks?

When working in languages such as C#, managing memory is not a problem. However, this can lead to difficult-to-find memory bugs in other languages. Is it safe to use the following code snippet in Typescript or Javascript without encountering any issues? c ...

The router is unable to direct when an item is clicked

I am encountering an issue with my routing setup - when I click on an item in the list-item component, it does not successfully route to the detail-component. Here is a glimpse of my source code: product-list.component.html: <h1>Product List Compon ...

I'm encountering issues with undefined parameters in my component while using generateStaticParams in Next.js 13. What is the correct way to pass them

Hey there, I'm currently utilizing the App router from nextjs 13 along with typescript. My aim is to create dynamic pages and generate their paths using generateStaticParams(). While the generateStaticParams() function appears to be functioning corre ...

Encountering a situation where the data retrieved from Firestore in Angular TypeScript is returning as

I am encountering an issue with retrieving the eventID value from my Events collection in Firestore. Although I am able to successfully display all the events in my app, I require the eventID for additional functionalities. As someone new to TypeScript an ...

Adjusting the interface of a third-party TypeScript library

I am currently working on modifying a third-party interface. I'm curious about why this particular code is successful: import { LoadableComponentMethods as OldLoadableComponentMethods } from '@loadable/component'; declare module "load ...

Structural directive fails to trigger event emission to parent component

Following up on the question posed here: Emit event from Directive to Parent element: Angular2 It appears that when a structural directive emits an event, the parent component does not receive it. @Directive({ selector: '[appWidget]' }) export ...