TypeScript compilerOptions.types with a module

I have a JavaScript project, which can be reduced to this:

console.log(depA());
console.log(depB());

I am pulling in the dependencies by using the plain old script tag:

<html>
  <head>
    <script src="pkg/depA/index.js"></script>
    <script src="pkg/depB/index.js"></script>
    <script src="src/index.js"></script>
  </head>
</html>

The packages I am using are JavaScript packages, both are like this:

function depX() { // Where X is A or B
  return 'depX';
}

Both of these packages have their own index.d.ts typings alongside theirindex.js`.

For package A this is straightforward:

declare function depA(): string;

For package B, which can also be used as a module, the typings are also a module file:

declare function depB(): string;
export default depB;

Like I said, this is a JavaScript project and I am using TypeScript for type checking:

{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "noEmit": true,
    "types": [
      "../pkg/depA",
      "../pkg/depB",
    ]
  }
}

I a referencing the package typings using compilerOptions.types, because this being a plain JavaScript project, I am not using NPM or anything to install the packages or the typings, and since this is JavaScript code, there are no import statements or requires (I am not using ESM and there is no build process for CJS), so TypeScript cannot be aware of the packages and know to pull in their types in any other way but being told explicitly using compilerOptions.types.

The problem is, typings for the package B communicate it is a module file, so the depB function is declared on the module scope, not the window scope. depA and depB are only demonstrations, there are real world packages with which I am facing this, so I have no option of removing the export default and making the typings a non-module file, but I can create a pull request and add support for making the typings file both a window-global function and a module other consumers whole use modules can import.

My question then is, how can depB typings be amended so they remain a module file (and no existing consumers are broken), but they also communicate the depB function exists on the window scope, so TypeScript knows it's okay to just call it in-place, standalone, no import, no nothing?

At runtime, that is the case, so the typings are the problem, they just don't communicate this for TypeScript to know (I think), so I ask, how can they be changed to do so?

I've made a GitHub repository which demonstrates the problem, there is an associated public Azure Pipeline which runs the TypeScript type check which shows depA is accessible with no problem, but depB is invisible at window as it is communicated as only being a module.

Upvotes: 0

Views: 754

Answers (1)

Karol Majewski
Karol Majewski

Reputation: 25790

The pattern you are describing is often used to define UMD packages. Their contents can be imported or — if there is no module bundler — accessed from the global scope.

TypeScript handbook explains what their definitions should look like. Your case is defined in the module-function.d.ts template, which can be reduced to:

depB.d.ts

declare function depB(): string;

export as namespace depB;
export = depB;

Including the above in your project did the job for me, and no upstream changes were required.

See the pull request.

Upvotes: 1

Related Questions