Skip to content

Fixed: TypeScript scripts importing the same module as a global script, #2341 - #2408

Open
GermanBluefox wants to merge 5 commits into
masterfrom
fix/ts-import-collision-2341
Open

GermanBluefox wants to merge 5 commits into
masterfrom
fix/ts-import-collision-2341

Conversation

@GermanBluefox

@GermanBluefox GermanBluefox commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Fixes the remaining case from #2341: a TypeScript script that imports the same module as a global script (or two global scripts importing the same module) failed with

const rxjs_1 = require("rxjs");
      ^
SyntaxError: Identifier 'rxjs_1' has already been declared

Global scripts and the script itself run as one vm.Script, and TypeScript emits the same CommonJS binding for every script that imports a module.

Based on the two fix commits from @agross (fork, branch issue-2341), authorship kept. The first two commits of that branch were not taken: they only touched stale build/src output, and master already contains a more complete fix for the typings part.

Changes

  • wrapCompiledScriptInCommonJsScope: compiled non-global TS scripts run in their own function scope (.call(exports)).
  • wrapCompiledGlobalScriptInCommonJsScope: every compiled global TS script gets its own scope and exports object. Its exports are published on globalThis as getter/setter, so later changes to exported let variables stay visible.
  • Applied in all three places in main.ts. common.compiled stays unwrapped, so the compile cache is unaffected.
  • Tests in test/testTypeScript.js: one reproduces the error without wrapping; the others cover global + normal script, two globals plus a live let export, and the line numbers.

Line numbers in error messages

Both wrappers keep the line numbers unchanged (checked by comparing the stack traces before and after):

Error in before after
a normal TS script :6:11 :6:11
a global TS script :8:11 :8:11
  • Normal script: the source starts on the first line of the wrapper. globalScriptLines is counted after the global scripts are combined, so fixLineNo still works.
  • Global script: the wrapper only takes the first line of the compiled source and the line after it, which is empty without the wrapper. A first version of the wrapper added about 13 lines per global script. That moved the line numbers of errors in all following global scripts (fixLineNo does not correct lines inside the global part).
  • One extra frame in stack traces: the wrapper call, e.g. at script.js.x:10:4, pointing at the line after the script. The frame above now reads at Object.<anonymous> (…:8:1) instead of at …:8:1. Only cosmetic.
  • Debugger: breakpoints work on the real VM source and stay correct, but the wrapper text is visible at the start and end of the script.

Behaviour changes to be aware of

  • Exported const/let from global TS scripts are now reachable as globals. Before, they only ended up on sandbox.exports.
  • Compiled TS scripts now really run in strict mode. Before, their "use strict" stood in the middle of the combined code and had no effect. In the other direction, a "use strict" from a leading global TS script no longer makes the following JS code strict.
  • Top-level this in a TS script is now exports ({}) as in real CommonJS, instead of the sandbox context.
  • Functions from global TS scripts are no longer hoisted across script borders. A global script that calls a function of a later global script while it loads now gets a ReferenceError.
  • Name clash with a global JS script: there is no Cannot redefine property error (checked: the properties of the VM context stay configurable). But the order changes: with var foo = 1 in a JS global and function foo in a later TS global, foo is now the TS function. Before, it was 1.

Tests

  • test/testTypeScript.js and test/testResolveTypings.js: 37 passing.
  • The Test JS integration tests could not run locally because an ioBroker instance was already running on the machine.
  • Blockly tests failing locally because of outdated editor deps (Blockly 11 installed, ^13.4.0 required); not related to this change.

🤖 Generated with Claude Code

agross and others added 3 commits October 16, 2026 20:40
Run non-global TypeScript output in a CommonJS-like scope to prevent
generated import bindings from colliding with bindings from shared scripts.
Run each compiled global script in a separate CommonJS scope and publish
its shared exports globally to prevent generated import-name collisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GermanBluefox and others added 2 commits October 16, 2026 21:26
…2341

The wrapper of a global script now only takes the first line of the compiled
source and the line after it, which is empty without the wrapper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@GermanBluefox

Copy link
Copy Markdown
Contributor Author

@agross this PR takes your two commits b03172f and 25ffad0 (authorship kept), with a changed wrapper for global scripts so the line numbers in errors stay the same.

Could you test the branch fix/ts-import-collision-2341 with your real scripts (rxjs in global and normal scripts) before I merge? Please watch for the behaviour changes listed in the description. The main ones: strict mode is now really active in TS scripts, and functions from a later global script are no longer available while an earlier global script is loading.

cd /opt/iobroker
npm i ioBroker/ioBroker.javascript#fix/ts-import-collision-2341
iob restart javascript

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants