Problem / Question
I'm using the TS-powered JS checking abilities of VSCode to type-check a bunch of JS files. These are files that are going to be imported via <script>
tags in HTML, and contain no export/import statements, so they truly are script
files, not module
files.
Normally, with script files you cannot redeclare a block-scoped variable. E.g.:
file_A.js
:let myStr = 'hello';
file_B.js
:let myStr = 'hello'; // <-- Error, Cannot redeclare block-scoped variable 'myStr'
However, these files have natural separation, due to their file structure:
- jsconfig.json (or tsconfig.json)
- dir_A/
- index.html (uses file_A.js via script tag)
- file_A.js
- dir_B/
- index.html (uses file_B.js via script tag)
- file_B.js
Is there an easy way to avoid this TS error...
Cannot redeclare block-scoped variable 'myStr'.ts(2451)
... by telling VSCode / TSC that although file_A
and file_B
both declare the same variable (let myStr = 'hello'
), it is not a re-declaration, because these files are never executed / imported within the same scope (there is no HTML file or script that executes both file_A.js
and file_B.js
)?
Solutions that work, but are a pain are:
- Put a config file in each sub-directory, remove the root config
- Encapsulate the code in each JS file with an IIFE
- Change variable declarations to use
var
- Add empty export (
export {}
) to each.js
file to force it to be a module, then have build step that removes that line as it copies files into/dist
I'm hoping there is something to do with either ambient declaration files or the config to tell TS to essentially treat these as modules, despite them having no import / export declarations.
I also realize that I can add an empty export declaration (export {}
) to each file to convert it into an actual module, but then I have to use <script type="module">
in the consuming HTML, which is not ideal for my given scenario (I lose legacy browser support, as well as the ability to use automatic top-level scoped variables).
TLDR;
The main crux of my question is: "is there any easy way to tell TSC to treat script files as modules (since they will be consumed that way) despite them having no import / export / module syntax?"
Full Reproducible Example
Files:
.
|-dir_A
| |-file_A.js
|-dir_B
| |-File_B.js
|-tsconfig.json
I left the
index.html
files outside of the above tree, because they don't affect TSC, and the error occurs with or without them.
Both file_A.js
and file_B.js
contain identical code:
let myStr = 'hello';
I've tried multiple variations of my config, but the minimal config is:
{
"compilerOptions": {
"checkJs": true,
"allowJs": true,
"noEmit": true,
}
}
I've also tried this with
"isolatedModules": true
, but that doesn't work.
EDIT: Closest answer so far
After some additional searching, I think I found what is closest to an "official" answer, although it is still unsatisfactory in that there is no good solution at the moment.
This thread, issue #18232 is basically the main discussion thread, as it discusses the issue of file scoping with scripts vs modules, and how TS can't know exactly how a file is going to be consumed. The issue is still open, and led me to discover some other very relevant links:
- This issue / feature request (#27535) is pretty much my exact desired solution; a TSC flag / option that would let me tell it to treat script files as modules. It was closed, because it overlaps with the thread I linked to above.
- This StackOverflow question, which has one of my "unsatisfactory" solutions; just use modules instead of scripts (this is not addressing the issue)
- TC39 Proposal - "Modules Pragma"
- This is probably the optimal solution; pragmas can be understood by both TSC and the browser, and they don't break older interpreters / engines (unlike the empty
export {}
hack, which requires module support)
- This is probably the optimal solution; pragmas can be understood by both TSC and the browser, and they don't break older interpreters / engines (unlike the empty
Sadly, the thread above has not seen traction in over a year.