【问题标题】:How to make TypeScript output valid ES6 module import statements?如何使 TypeScript 输出有效的 ES6 模块导入语句?
【发布时间】:2018-02-06 12:31:30
【问题描述】:

All major browsers 支持 ES6 模块已有一段时间了。

这些与许多服务器端方法的不同之处在于它们需要指定要从中导入的确切文件 - 它们不能使用文件发现。

这是有道理的——在 Node 应用程序或像 WebPack 这样的打包工具中,它们只需要模块的名称,然后可以花费一些额外的时间来发现包含代码的特定文件。在网络上可能会浪费很多往返行程('library'library/index.js,或library/library.js,或library.jsrequire() 不在乎,但在网络上我们必须这样做)。

TypeScript 支持 ES6 模块(在 tsconfig.json 中设置 "module": "es6"),但它似乎使用了文件发现方法...

假设我有library.ts:

export function myFunction(...) {  ... }

然后在app.ts:

import {myFunction} from './library';
var x = myFunction(...);

但是,在转译时这并没有改变 - TS 输出仍然具有用于文件发现的 'library' 名称,这不起作用。这会引发错误,因为找不到 'library'

<script type="module" src="app.js"></script>

为了让 ES6 模块工作,TS 输出需要引用特定文件:

import {myFunction} from './library.js';
var x = myFunction(...);

如何让 TS 输出有效的 ES6 模块 import 语句?

注意:我不是询问如何让捆绑器将 TS 输出连接到单个文件中。我特别想使用&lt;script type="module"&gt;单独加载这些文件

【问题讨论】:

  • ...from 'library';...from './library.js'; 是两个不同的东西,第一个是命名包,第二个是模块的相对路径
  • @MaximKoretskyi 是的 - 命名包有一个入口点,它需要是模块导入的显式相对或绝对路径。网络不能使用包名。
  • 如果使用命名包导入,TS无法输出相对路径,因为库与导入模块不在同一目录中,对吧?
  • @MaximKoretskyi 没关系,无论哪种方式,路径都不起作用。 ./library 失败,../src/utils/library 失败,https://example.com/cdn/library 失败。
  • 什么时候失败?在浏览器中?

标签: javascript typescript es6-modules


【解决方案1】:

编译器采用模块种类标志:

--module ES2015

您还需要针对 ECMAScript 6 / 2015...

--target ES2015

您需要模块类型和编译目标至少为 ECMAScript 2015 才能实现“零转换导入”。

您的导入语句应该介于两个示例之间:

import {myFunction} from './library';

附加说明

显然还有很多关于模块解析的讨论......有TC39 specification,还有WHATWG specification - 加上Node目前仍然没有文件扩展......看起来RequireJS可能比我们都认为...请看:

The TypeScript thread for supporting file extensions during import transpilation(即是否会添加文件扩展名?)。

推荐

坚持使用模块加载器,例如 RequireJS 或 SystemJS。这也意味着您的模块可以通过分别使用 UMD 或 System 模块类型在浏览器和服务器之间共享。

显然,一旦 ECMAScript 讨论得出结论,这将需要重新审视。

【讨论】:

  • 这有点好,但不起作用。在支持的浏览器(比如 Canary)中尝试它,你会得到一个不存在的文件“库”的 404。 TS输出需要指定符合ES6模块规范的文件,所以import {myFunction} from './library.js'.js。
  • "不需要.js 扩展" - 这取决于entirely on the filename under which the script is stored
  • @Bergi 这正是我的观点。在 ES6 模块规范中,.js 需要的所有主要浏览器都已接近实现。如果启用 ES6 模块(通过命令行上的--module ES2015 或配置中的"module": "es6"),则 TS 将library.ts 转换为library.js - 因此编译工具确切地知道特定文件,浏览器不会.
  • @Keith 啊,我认为编译器不知道会弄乱模块名称。
  • 不——我认为我们不同意这一点。我在许多不是专门针对 Node 或浏览器的库/框架上工作 - 所以我编写 ES 样式导入并让 TypeScript 使用 UMD 标志转译它们。这允许我将库打包为 .js + '.d.ts` 包,可以在浏览器或 Node 上使用,而不仅仅是在其他 TypeScript 程序中使用。我们希望在这样一种情况下,我们可以编写一个独立于它将在哪里运行的模块。
【解决方案2】:

这是一个bug in TypeScript,尽管关于是否应该修复它存在一些争论。

有一个解决方法:虽然 TS 不允许您将 .ts 文件指定为模块的源,但它允许您指定 .js 扩展名(然后忽略它)。

所以在app.ts:

import {myFunction} from './library.js';
var x = myFunction(...);

这会在app.js 中正确输出,并且TS 已正确找到import 定义和绑定。

这有一个优点/需要注意/小心:TS 只是忽略 .js 扩展名,并使用通常的文件发现加载路径的其余部分。这意味着它将导入library.ts,但它也会找到library.d.ts 之类的定义文件或导入library/ 文件夹中的文件。

如果您将这些文件一起加入到 library.js 输出中,最后一种情况可能是可取的,但要做到这一点,您将要查看大量嵌套的 tsconfig.json 文件(混乱)或可能另一个库的预编译输出。

【讨论】:

  • 如果您使用 Visual Studio Code,您可以将其配置为在自动导入模块时自动添加 .js 结尾。转到设置,然后在搜索字段中键入“模块说明符”。您可以找到选项“typescript > Preferences: Import Module Specifier Ending”,您可以将其设置为“.js”。
【解决方案3】:

对于个人项目,我采取了另一种方式。由于我有 NPM 调用 shell 脚本将 index.html 复制到 /build 文件夹,因此我有 shell 脚本,然后批量重命名所有 .js 文件以完全没有扩展名。

我确实必须在“MIME 类型”部分通知 IIS,无扩展名的文件应该具有该特定站点的 MIME 类型 application/javascript,但它确实有效。没有 webpack,没有 SystemJS,什么都没有。 Index.html 只是有一个硬编码

    <script type="module">
      import "./app";
    </script>

这很重要,因为我使用的测试框架 jest 不喜欢我将 .js 放入打字稿导入语句中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-07-06
    • 2015-07-27
    • 1970-01-01
    • 2015-08-13
    • 2016-12-11
    • 2018-03-08
    • 1970-01-01
    • 2013-05-03
    相关资源
    最近更新 更多