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:
var
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).
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?"
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.
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:
export {}
hack, which requires module support)Sadly, the thread above has not seen traction in over a year.
There is now an official solution for this problem, which does not require workarounds (like empty exports)! TypeScript has added a compiler option (in v4.7), moduleDetection
, which changes how files are treated as modules (or not) by default.
To fix the issue outlined in the original question and have each .js
file treated as a module without requiring explicit imports or exports be added, make sure the tsconfig.json
file has "moduleDetection": "force"
, like so:
{
"compilerOptions": {
"checkJs": true,
"allowJs": true,
"noEmit": true,
"moduleDetection": "force"
}
}
For more details on this feature, see: