javascript - 在 TypeScript 中使用外部模块声明全局变量
我正在用 TypeScript 编写一个 npm 模块 https://www.npmjs.com/package/html2commonmark。该模块可以在 nodejs 中使用(通过使用 require),也可以在浏览器中使用(通过在浏览器中加载 node_modules/html2commonmark/dist/client/bundle.js)。
我最近添加了 *.d.ts 文件,以便在使用 "moduleResolution": "node" 时获取类型信息。这效果很好,当安装我的模块时,它已可以在 TypeScript 中使用。因此:以下 TypeScript 代码段可以无错误地编译:
// After installing using npm install html2commonmark
// using "moduleResolution": "node"
import * as html2commonmark from 'html2commonmark';
let converter = new html2commonmark.JSDomConverter();
美丽!
现在我想在浏览器中运行我的模块。如前所述,我需要在我的 index.html 页面中添加一个指向 node_modules/html2commonmark/dist/client/bundle.js 的 script 标签。之后,全局变量 html2commonmark 应该就可用了。问题是:我如何让 TypeScript 编译器知道这个全局变量存在?下面这段 TS 代码无法编译:
let converter = new html2commonmark.BrowserConverter();
// error TS2304: Cannot find name 'html2commonmark'.
即使我添加了一个 global.d.ts 文件,我似乎也无法同时导入我的外部模块并声明我的全局变量:
// Something like this does not work :(
import * as b from 'html2commonmark';
declare var html2commonmark: typeof b;
我理解这是为什么。通过使用 import 关键字,我的 ts 文件被转换为一个外部模块,因此需要被导入。然而,我觉得我的场景是一个常见情况。也就是:一个 npm 模块包含一个 npm 组件和为浏览器打包的文件,该文件将功能作为全局变量暴露出来。
有没有办法使用我外部模块中的定义来声明全局变量?我不想把我的 API 重写成(DefinitelyTyped 风格的)命名空间,而我刚刚已经用 TS 写完整个源代码…
答案#1
编写一个能够在浏览器和 Node.js 中使用的外部模块的正确方式是:
- 使用
--module umd标志,然后在浏览器中使用 AMD 加载器来加载模块 - 使用
--module commonjs标志,然后使用像 webpack 或 browserify 这样的打包工具
换句话说,当你开始编写模块化代码时,盲目地向 HTML 文件添加 <script> 标签以在浏览器中加载代码不再是正确的方法。正如你不应该在 Node.js 中修改全局作用域一样,你也不应该在浏览器中修改全局作用域。你可以用 if (typeof window !== 'undefined') (<any>window).global=yourObject; 来进行黑客操作,但说真的,绝对不要这样做,这是错误的,而且会导致程序崩溃。
至于将外部模块中的接口暴露到全局作用域,这是不可能的,因为你必须 从 TypeScript 1.8 起,你可以通过在模块内部使用 import 外部模块才能获取其接口的引用,而此时你已经创建了另一个外部模块。declare global 来扩展全局类型。