TypeScript模块导入和WebPack

3

我在使用Webpack将TypeScript编写的项目中导入依赖时遇到了一些问题。我的第一个问题是让TypeScript识别导入的模块。

我有一个header.ts文件,声明了一个嵌套在vi.input下并导出了一个VIInputDirective类的模块。在main.ts文件中,我尝试从header.ts文件中导入导出的VIInputDirective类,但似乎无法让TypeScript识别它。

header.ts

module vi.input.header {
  import IDirective = angular.IDirective;

  export class VIInputDirective implements IDirective {
    ...
  }
}

main.ts

import {VIInputDirective} from "./header.ts"; // Nothing imported; Cannot Resolve issue
VIInputDirective.whatever(); // Does not work

material.dt.s

declare module vi.input {
  ...
}

如果我在main.ts文件中将import {VIInputDirective} from "./header.ts";替换为import VIMHeaderDirective = vi.input.header.VIInputDirective;,那么它可以正常工作,但是在转译/注入时,webpack会给我以下错误:
VM1469:1Uncaught ReferenceError: vi is not defined

我已经尝试直接导出 vi.input.header 模块 (即 export module vi.input.header),但没有成功。我也尝试使用引用语法来包含文件,但也没有成功:///<reference path="file_path"/>
这是由于模块嵌套的问题。如果我删除模块并直接导出 VIInputDirective 类,它可以正常工作。然而,我想保持它作为一个嵌套模块。

你试过使用import * as x from "./header"然后创建new x.vi.input.header.VIInputDirective吗? - Banners
也许你应该尝试在模块声明中添加一个导出语句(TypeScript 中现在改为“命名空间”)。 - Banners
刚刚尝试了这个代码:"import * as VIInputDirective from "./header.ts",但是TypeScript无法找到该模块。我收到了“无法解析文件”错误。如果我不将header.ts包装在一个模块中(即module vi.input.header {}),它可以正常工作,但我想保留它。 - sags
抱歉,我现在没有电脑可以给你一个可行的答案。这是一个有用的帖子https://dev59.com/7V0a5IYBdhLWcg3waYCM - Banners
将 "module vi.input.header" 更改为 "export namespace vi.input.header",结果相同。 - sags
2个回答

6
您没有使用模块。模块具有一个或多个顶层importexport语句。相反,您正在使用全局命名空间并创建全局命名空间的子命名空间来组织程序。这被称为揭示模块模式。它不涉及使用模块。
不幸的是,TypeScript 曾经将此模式称为使用 内部模块。这种术语已经被弃用,并且强烈不建议使用module x {} 语法(注意 x 周围缺少 ")。该语言引入了一个同义词关键字 namespace,以减少混淆。
JavaScript 加载器和打包工具,如 Webpack、RequireJS 和 SystemJS,可以处理模块,这就是 TypeScript 所谓的 外部模块
为了澄清,您提到的以下结构与模块无关。
  1. top level, non-exported module/namespace syntactic declarations

    module vi.input.header { ... }
    

    this would now be written as

    namespace vi.input.header { ... }
    

    in order to minimize confusion but, irregardless, the emit has always resulted in.

    var vi;
    (function (vi) {
        var input;
        (function (input) {
            var header;
            (function (header) {
            })(header = input.header || (input.header = {}));
        })(input = vi.input || (vi.input = {}));
    })(vi || (vi = {}));
    

    Note this mutates the global scope in a pattern commonly used by various libraries. namespaces (formerly called internal modules) like the one above have the interesting property that multiple files can contribute to their contents, and this is in fact their primary purpose. This explains the degree of nesting and the conditional assignments to variables in the emit above. This has nothing to do with using Modules.

  2. import assignments that reference namespace members such as

    import IDirective = angular.IDirective;
    

    do not qualify as top level import declarations, and thus do not cause their containing file to be considered a module. This is true even if they are placed at the top level of a file. The reason is that module systems, be they AMD, CommonJS, System, or ES2015, all use strings as module specifiers, importing from such strings; which incidentally may represent file paths, urls, resolved simple names, or synthetic module ids.

再次强调,代码中的import name = qualified.global.name语句是TypeScript特有的功能,与模块无关。它们非常有用,可以为嵌套类型和值取别名,但并不能创建模块。

现在我们来谈谈有趣的部分,也是与您的具体问题相关的部分: namespace可以被使用,并且有时候在外部模块中使用起来非常优雅,但它们的语义是非常不同的。

请考虑以下内容:

services.ts

export namespace app {
  export class SomeService { }
}

这将编译成以下JavaScript代码:

export var app;
(function (app) {
    class SomeService {
    }
    app.SomeService = SomeService;
})(app || (app = {}));

main.ts

export namespace app {
  export function bootstrap() { }
}

这将编译成以下JavaScript代码:

export var app;
(function (app) {
    function bootstrap() { }
    app.bootstrap = bootstrap;
})(app || (app = {}));

上述两个都是外部模块,也就是真正的模块,它们使用命名空间作为内部代码组织机制,但关键是它们不会共享的app命名空间造成影响,每个模块都有自己的文件范围内的app变量。它们都不能隐式访问对方的成员,namespace app的声明不会跨文件合并,它们具有相似的内部命名方案只是模块化的副作用。
那么这一切与你的问题和你尝试应用的建议有什么关系呢?
我们来看看 headers.ts
module vi.input.header {
  import IDirective = angular.IDirective;

  export class VIInputDirective implements IDirective {
    static whatever() { }
  }
}

如上所述,此文件不是一个模块,而是使用全局命名空间来公开其声明。如果我们今天编写这个文件,我们会使用namespace关键字而不是module关键字,按照惯例。

main.ts

import {VIInputDirective} from "./header.ts"; // Nothing imported; Cannot Resolve issue
VIInputDirective.whatever(); // Does not work

事实上,这两种方式都不起作用,因为你像导入模块一样导入了 headers.ts,但正如我们刚刚看到的,它并不是一个模块。此外,第一行代码是一个顶级 import 语句,它从一个模块指定字符串中导入,具有讽刺意味的是,它使得 main.ts 本身成为了一个模块。
简而言之,这两种风格不太相容,并且在它们之间共享代码并不简单,也不是你应该尝试做的事情(为了保持相对简单,我已经忽略了UMD格式)。
现在我们来到了完整的循环。 强调文本
declare module vi.input {
    ....
}

如果我在main.ts文件中使用import VIMHeaderDirective = vi.input.header.VIInputDirective;替换import {VIInputDirective} from "./header.ts";,那么它可以正常工作,但是当转译/注入时,webpack会给出以下错误提示:
正如上面所解释的那样,这个import并没有针对一个模块指定符字符串,而且在任何其他顶级导入或导出的情况下都不存在,改变了main.ts文件,使其不再是一个模块。这会导致TypeScript正确地进行类型检查,使用import = namespace.value互相引用全局变量是完全合法的,但这些不是模块,JavaScript工具(比如Webpack)操作的是模块。
那么,在这个崭新的世界里,你将如何编写这个应用程序?既然您正在使用一个模块捆绑工具Webpack,那么您应该一直使用适当的模块来编写它。
main.ts
import {VIInputDirective} from "./header.ts";
VIInputDirective.whatever();

headers.ts

import {IDirective} from 'angular';

export class VIInputDirective implements IDirective {
  static whatever() { }
}

material.d.ts文件现在与您编写时的不同,因为它已更新为使用正确的模块。如果您需要引用其中的内容,请使用模块语法。

my-dialog-options.ts

import {material} from 'angular';

const dialogOptions: material.IDialogOptions = { ... };

export default dialogOptions;

我尽力避免过于简化,但为了避免写一篇长篇小说,有些手势必不可少。我相信我已经涵盖并传达了关键要点。

0

你要找的是命名空间而不是模块。

header.ts

export namespace vi.input.header {
  export class VIInputDirective {

  }
}

main.ts

import { vi } from "./header.ts"; 
var foo = new vi.input.header.VIInputDirective();

在将header.ts更新为_export namespace_并使用您的导入语法后,TS编译器会抛出“找不到名称header”的错误。这可能是由于我的material.dt.s文件中声明了_declare module vi.input {}_所致吗? - sags
我已经尝试了所有的方法,除了删除命名空间之外似乎没有其他办法可行。 - sags

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接