简体   繁体   中英

TypeScript: Using a plain JavaScript lib in web project, with custom typings

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM