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
, but it did not resolve the issue."isolatedModules": true
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)
- This proposal seems optimal; pragmas can be interpreted by both TSC and browsers without breaking compatibility with older interpreters/engines (unlike the empty
Regrettably, activity on the aforementioned thread has waned over the past year.