2

I have a website project that uses TypeScript and Webpack. Now I need to use an external proprietary JavaScript library in this project. This is my project structure:

├── dist
│   ├── foo.js
│   ├── main.js
│   └── index.html
├── src
│   ├── foo.d.ts
│   └── index.ts
├── webpack.config.js
└── tsconfig.json

File dist/main.js is generated by Webpack that uses ts-loader to compile the entry TypeScript file src/index.ts. Both foo.js and main.js are referenced in the dist/index.html file, using html script tag:

<script src="foo.js"></script>
<script src="main.js"></script>

The dist/foo.js library is a plain old JavaScript file, it is not an ES6 module, it just contains a bunch of classes.

I have created the foo.d.ts file with typings to assist TypeScript in type-checking the use of the classes defined in foo.js:

// src/foo.d.ts
export declare class MyClass {
  constructor(message: string);
}

But I have trouble registering it properly so that both TypeScript and Webpack deal with this setup properly. The problem lies in how TypeScript loads the type declarations file. If I refer to it with relative path, TypeScript can see and use it, but Webpack complains that it cannot resolve it:

This index.ts compiles, but Webpack gives error:

import { MyClass } from './foo';
const c = new MyClass('Hello, world!');

Webpack error: Module not found: Error: Can't resolve './foo' in '/path-to/src'

The reason for Webpack failure apparently is that it is looking for the foo code and fails to find it, as the compiled JavaScript contains require("./foo").

So I need to replace this line

import { MyClass } from './foo';

with something that tells TypeScript not to include this require() call in the compiled code. I think this problem is described in this ts-loader discussion, but I am not sure what's the final verdict there. If I import like this

import { MyClass } from 'foo';

the TypeScript compiler gives error Cannot find module 'foo' or its corresponding type declarations.. Apparently something has to be specified in tsconfig.json for TypeScript to know where to look for the type declarations. But adding "include": ["src/**/*"] doesn't seem to change anything.

1 Answer 1

1

The problem is that you're lying about how you're really getting it (well, not really, you just don't know how to spell "this exists in the global scope" in your .d.ts file).

At runtime you're just relying on MyClass to exist somewhere up your scope chain, so you need to tell TypeScript that the environment you're working in is that kind of environment. The way you do that is with an ambient module declaration that modifies the global scope's type definition - which is very similar to your definition except you don't use the export keyword:

// src/foo.d.ts
// Note the lack of `export` here
declare class MyClass {
  constructor(message: string);
}

Alternatively, if you're using window.MyClass, then you use a global declaration:

declare global {
  interface Window {
    MyClass: MyClass
  }
}

declare class MyClass {
  myMethod(): void
}

Then, in your code, you can just new MyClass("Hello world") and TypeScript will have your back.

Sign up to request clarification or add additional context in comments.

2 Comments

From the TS page on global .d.ts, I confirm that's the case with my library. But I wouldn't mind if I had to do import {MyClass} from 'foo', that would leave me with less polluted global scope. The problem with your suggestion (of not having import at all) that tsc doesn't load the type definitions. I just get Cannot find name 'MyClass' error. How do I point TS to the d.ts file, or should it be specifically named/placed?
I think you need to configure typeRoots as well, or baseUrl + include. See elfi-y.medium.com/typescript-ambient-module-8816c9e5d426

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.