Typescript: 提供一种将“.js”文件扩展名添加到模块说明符末尾的方法

创建于 2017-06-16  ·  273评论  ·  资料来源: microsoft/TypeScript

为了在浏览器中使用 es6 模块,您需要一个 .js 文件扩展名。 但是输出不会添加它。

在 ts 中:
import { ModalBackground } from './ModalBackground';
在 ES2015 输出中:
import { ModalBackground } from './ModalBackground';

理想情况下,我希望将其输出
import { ModalBackground } from './ModalBackground.js';

这样我就可以在 Chrome 51 中使用输出

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Webpack boilerplate</title>
  <script type="module" src="index.js"></script>
</head>
<body></body>
</html>

image

https://github.com/Microsoft/TypeScript/issues/13422相关

ES Modules Needs Proposal Suggestion

最有用的评论

另外,为了概括这个问题,我认为这实际上并不是要添加.js扩展名,而是将模块说明符解析为实际路径,无论扩展名是什么。

所有273条评论

它不仅与#13422 有关,而且是同一个问题。 但是,尽管我认为这是一个重要的问题,但反应一直很消极,所以希望你的问题能得到更好的接受。

好吧,我希望添加它,我们真的很期待在我的下一个 TypeScript 播客中讨论我的 POC,但看起来我们将不得不等待使用没有构建工具的 TypeScript。

目前 TypeScript 不会重写路径。 这肯定很烦人,但您目前可以自己添加.js扩展名。

@justinfagnani @rictic

感谢您的提示,我将编写一个 shell/node 脚本来执行此操作。

@DanielRosenwasser在标签下收集本机 ES6 模块问题是否有意义?

另外,为了概括这个问题,我认为这实际上并不是要添加.js扩展名,而是将模块说明符解析为实际路径,无论扩展名是什么。

我遇到了另一个不是打字稿领域的问题,但它适用于我的用例。

我不确定如何处理 node_modules。 通常 webpack 通过 ts-loader 将它们捆绑到代码中,但很明显,浏览器不理解:

import { KeyCodes } from 'vanilla-typescript;
https://github.com/quantumjs/vanilla-typescript/blob/master/events/KeyCodes.ts#L3

在这里添加一个js扩展是没有意义的。

我想必须通过打字稿或在服务器上运行的 url 解析器进行路径扩展。

我很欣赏它是一个相当小众的案例,但我认为这将是 TS 在该领域早期大放异彩的一种方式。 也许它可能是 tsc 编译器的插件?

对于任何来到这里并想要一个临时解决方案的人,我编写了一个脚本来添加一个 js 文件扩展名来导入语句:

"use strict";

const FileHound = require('filehound');
const fs = require('fs');
const path = require('path');

const files = FileHound.create()
  .paths(__dirname + '/browserLoading')
  .discard('node_modules')
  .ext('js')
  .find();


files.then((filePaths) => {

  filePaths.forEach((filepath) => {
    fs.readFile(filepath, 'utf8', (err, data) => {


      if (!data.match(/import .* from/g)) {
        return
      }
      let newData = data.replace(/(import .* from\s+['"])(.*)(?=['"])/g, '$1$2.js')
      if (err) throw err;

      console.log(`writing to ${filepath}`)
      fs.writeFile(filepath, newData, function (err) {
        if (err) {
          throw err;
        }
        console.log('complete');
      });
    })

  })
});

我可能会把它变成一个cli工具..

@justinfagnani的评论一针见血。

另外,为了概括这个问题,我认为这实际上并不是添加 .js 扩展名,而是将模块说明符解析为实际路径,无论扩展名是什么。

当你写

import { KeyCodes } from 'vanilla-typescript';

或就此而言

import { KeyCodes } from 'vanilla-javascript';

您正在从模块说明符导入,它可能是也可能不是文件,但在这种情况下将.js添加到末尾不太可能导致有效的解析。

如果您正在编写 NodeJS 应用程序,那么 NodeJS Require 算法将尝试各种解析,但它可能_不会_尝试将其解析为'vanilla-typescript.js' ,因为它引用了一个抽象名称,并且按照约定和配置将被解析(也许通过各种尝试)到类似'../../../node_modules/vanilla_typescript/index.js'的东西。

其他环境(例如 AMD)在如何执行此解析方面存在差异,但所有这些环境的共同点是抽象模块说明符的一些概念。

我提出这个是因为各种浏览器中的 ES 模块实现实现了一些不完整的东西。 如果我们甚至考虑我们最简单的依赖关系,并且一旦我们提出传递性依赖关系的主题,就会很明显需要一种方法来配置 doggone 事物。

这可能还很遥远,但正如您所发现的那样,写给我们已经给出的这个(礼貌的)概念实现证明是不现实的。

此外,我看不出 TypeScript 在这里有什么帮助,因为这个问题是特定于环境的。

@QuantumInformation.js添加到路径的程序看起来不错,轻量级,甚至优雅,但您最终实现了自己的模块捆绑器。 这是一项有趣而有趣的工作,但它展示了浏览器中当前实现的缺陷。 即使你用纯 JavaScript 编写,你仍然需要一些东西来编译和打包你的传递导入的依赖项。

我基本上只是在抱怨已发布的 ES 模块的实现远远不够。

同样,NodeJS、RequireJS AMD、Dojo AMD、Sea Package Manager、CommonJS、Browserify、Webpack、SystemJS,都有自己不同的做事方式,但它们都提供抽象名称解析。 他们必须提供它,因为它是_基本的_。

感谢您阅读我的咆哮。

不确定是哪个版本的 TS 添加了它,但现在可以使用 ' ./file.js'之类的导入(即使文件实际上是 file.ts)。
TypeScript 可以很好地解析文件,并将完整的.js导入输出到目标。
lit-html使用它: https ://github.com/PolymerLabs/lit-html/blob/master/src/lib/repeat.ts#L15

从 TS 2.0 开始就有可能。 但是像 webpack 这样的工具不支持它,所以最终它是没用的。

如果在源(最常见的用例)上使用 ts-loader 是没用的。
仍然可以捆绑目标(通常是“dist”文件夹),因为实际的 js 文件存在于那里并且可以通过解析过程找到。

我想知道是否可以在ts-loader中实现快速转换,从目标代码中去除.js扩展,从而允许直接从源代码中捆绑。

随意这样做,那就太好了。 几个月前,我在主要的 webpack ts 加载器(如 ts-loader)上发布了这个问题,但我受到了非常糟糕的欢迎......

作为参考,rollup typescript 插件没有问题,证明它是可行的。

在浏览器加载器实现和 WGATWG 加载器规范至少支持 _some_ 配置之前,我看不到这有什么好处,因为大多数依赖项都无法正确加载。

从我的角度来看,这一切都不重要,直到在引用任意字符串文字说明符的导入上使用本机加载器是实际的,可能还不是 URL 的东西,并且经过转换产生实际网址。

在那之前,我们将继续依赖 SystemJS 和 Webpack 等工具。

我创建了一个小型转换器,可以从导入/导出语句中去除“.js”。
我使用tsutils类型的守卫,所以yarn add tsutils --dev 。 (如果您的项目中有tslint ,通常会安装该软件包,因此需要额外的依赖)

https://gist.github.com/AviVahl/40e031bd72c7264890f349020d04130a

使用它,可以捆绑包含来自以.js结尾的文件的导入的 ts 文件(使用webpackts-loader ),并且仍然将源代码转换为可以加载的 esm 模块浏览器(使用tsc )。

这可能有用的用例数量有限。

编辑:我更新了要点以与导出一起使用。 它很天真,没有经过优化,但是很有效。

在这个问题上有什么动静吗?

这个扩展问题让我们回到了 TypeScript 的开始,以及为什么需要tsconfig.json以及为什么在compilerOptions设置中添加了module选项。

由于extension的扩展问题只对 ES2015+ 很重要,因为 require 能够很好地解决,所以当目标代码是 ES2015+ 时,让它由编译器添加。

  1. .js.ts
  2. .jsx.tsx

你好,我来晚了,但想帮忙。 我无法理解这里的问题。 从 OP 示例来看,它是:

import { ModalBackground } from './ModalBackground';

是我们不知道'./ModalBackground'是什么的问题吗? 它可能是一个文件夹或其他东西?

如果我们在整个项目上运行tsc并且我们知道 ModalBackground.ts 存在,那么我们就会知道添加扩展是安全的,不是吗?

这个问题也是 RxJS 社区非常感兴趣的问题。解决这个问题的时间表是什么? 它甚至是优先级吗? 是否有任何第三方转换会有所帮助?

如果输出目标是 ES2015,我不确定这是否是一个问题,是吗? 这可能属于 ES2015 浏览器功能的范畴。 更重要的是, @justinfagnani我们不能将其作为一个值得担心的平台目标来推动吗? (可能需要分叉成单独的线程)。

更重要的是, @justinfagnani我们不能将其作为一个值得担心的平台目标来推动吗? (可能需要分叉成单独的线程)。

是的,当然很多人希望平台支持某种形式的裸说明符,但目前的事实是它不支持,甚至没有提议添加它们。 我们需要经历整个可能需要多年的过程。

即使我们最终获得了裸说明符支持,它也极不可能以原样的节点模块解析的形式出现。 因此,tsc 用于查找文件的解析算法与浏览器使用的解析算法(甚至可能节点的本机模块支持,afaik)之间存在不匹配。

如果 tsc 能够具体化它用来查找另一个模块的路径,这样下游工具和环境就不会解释具有冲突意见的说明符,那就太好了。

@justinfagnani他们已经被解释为相互矛盾的观点。 TS 生成一个 JS 文件,它生成的 JS 代码不指向该文件。 ES6 是最接近官方正确的 JavaScript 方法,所以如果 TypesScript 生成的 ES6 代码是错误的,这是一个简单明了的错误。 无需等待任何建议等,只需修复 TypeScript 的错误。 但是今天人们觉得,如果他们不挑毛病,在行动前通过10层建议,那么他们就没有理智地行动。 让我休息一下。

@aluanhaddad确实很多项目不会受益,但是有些项目不使用 npm 依赖项(或者能够以某种方式处理 npm 依赖项),所以这些项目会受益。

它对于编译为 ES6 的 TypeScript也非常有用。 现在这些库不能在浏览器中本地使用,但如果 TypeScript 输出.js扩展名,那么它们就可以工作。

@justinfagnani它还没有标准化或实现,但是有一个建议让 npm 包在浏览器中工作。

包名映射可以由 TypeScript 编译器或其他工具自动生成。

所以我再次重新审视这个问题,除了我的节点脚本之外,还有什么好的解决方法吗?

我一直在使用这个解决方案:
https://github.com/Microsoft/TypeScript/issues/16577#issuecomment -343610106

但我相信如果模块没有文件扩展名但使用正确的 MIME 类型提供服务,那应该可以解决。

这有什么动静吗?

也许https://github.com/Microsoft/TypeScript/pull/25073可以解决这个问题?

@Kingwl你会支持其他一些文件扩展名吗? 例如.mjs .es .esm

也许不是,这是另一个功能

这怎么可能? 打字稿编译器_知道_目标输出是一个 JS 文件。 我已经浏览了这些主题 15 分钟,但我仍然不明白为什么它省略了扩展。

根据对这个问题的引用数量,我认为它正在朝着正确的方向发展。 我很快就会再试一次。

这怎么可能? 打字稿编译器_知道_目标输出是一个 JS 文件。 我已经浏览了这些主题 15 分钟,但我仍然不明白为什么它省略了扩展。

有一些人们使用的常见用例,其中缺少扩展允许更灵活的基础架构。 Node require hook 和 webpack loader 就是这样的两种情况。

这怎么可能? 打字稿编译器_知道_目标输出是一个 JS 文件。 我已经浏览了这些主题 15 分钟,但我仍然不明白为什么它省略了扩展。

有一些人们使用的常见用例,其中缺少扩展允许更灵活的基础架构。 Node require hook 和 webpack loader 就是这样的两种情况。

浏览器模块都不关心这些。

添加一个选择加入标志来发出 .js 扩展名会杀死打字稿团队吗? 我们_do_知道我们在这里做什么,否则不会有十几个线程(打开和关闭)仍然获得回复和困惑的问题。 我们知道这不是 TS 的缺陷,但如果 TS 是为了解决我们的 JS 问题,请把这个问题添加到列表中。

免责声明:

是的,我知道这可能会导致大量的人在这里发布“你破坏了 webpack”的问题,但对那些人来说,真的很难接受。 它应该是可选的。

顺便说一句,挖掘 TypeScript 源代码,我看到importModuleSpecifierEnding - 这可以(ab)用于使发射器使用.js结尾吗?

也许将这个提议与 tsconfig 模式混为一谈? https://github.com/domenic/package-name-maps

在这一点上,如果 TypeScript 只能使用 tsconfig 中指定的paths自动重写导入,我会很高兴。 即使没有浏览器支持,也可能为我解决大部分痛点。

任何更新? 有没有尝试解决这个问题? 默认情况下,我们拥有所有支持 es 模块的主流浏览器,这些浏览器的标准要求扩展存在。 我知道我们可以手动添加它,tsc 会理解,但这不应该是强制性的,为什么它不能正常工作?
抱歉发牢骚,但这个问题现在已经很老了,还没有做任何事情......

这怎么可能? 打字稿编译器_知道_目标输出是一个 JS 文件。 我已经浏览了这些主题 15 分钟,但我仍然不明白为什么它省略了扩展。

有一些人们使用的常见用例,其中缺少扩展允许更灵活的基础架构。 Node require hook 和 webpack loader 就是这样的两种情况。

然后,也许 Typescript 编译器可以接受添加或不添加扩展的选项。 也许它甚至可以接受一组正则表达式来添加(或不添加)扩展,例如包含选项(https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)。

是的,它之前至少已经提出过一次,为什么不考虑一下呢? TS 已经有一些实验性功能,一旦在浏览器中实现,将来可能会发生变化,但 TS 已经为那些不稳定的规范设置了标志。 为什么不只是有一个实验标志addImportsExtensions ,它只会做module += '.js' ,就是这样! 没有时髦的逻辑,没有额外的功能。 如果您希望将其保留为单独的目标,则它可能是不同的目标。 更重要的是,如果您接受上述任何一项,我将亲自挖掘 tsc 代码并为此进行 PR,如果无论如何都不会被接受,我不想浪费我的时间。

您可以简单地使用自定义转换在发出的代码中重写导入(添加“.js”或使用模块导入的解析路径)。

如何编写 TypeScript 转换
ttypescript: tsc的包装器,在编译期间应用转换

也许已经有一个现有的转换可以满足您的需求。

我知道这是可能的,我已经玩过很多编译器 API(尽管没有我想要的那么多)。 问题是,它为项目添加了额外的工具,并且额外的工具有额外的维护者,他们可能会或可能不会足够快地响应问题,如果有的话(通常不是他们的错,我们都有生活和工作)。 小项目经常被放弃,自己做所有事情只是将责任转移到我们身上,所以我们花时间在工具上而不是处理业务代码。
考虑到上述情况,很多项目甚至不会将其视为解决方案,而是添加额外的构建步骤,如汇总等,这会增加额外的配置开销并让我们回到额外的维护成本。
总结一下:我更愿意把时间花在官方 TS 的 PR 上,我和社区都会从中受益,而不是将时间花在我最终可能放弃的定制解决方案上(由于没有时间或许多其他原因),或者世界上其他任何人都无法使用它。

@ajafff​​ 这可能是一个解决方案,但我认为主要问题是以下问题:Typescript 转译器在浏览器中生成错误的源代码。 进行转换很好,但我认为这是一种解决方法。 我想知道我是否必须关心转译细节并需要实现我自己的转换,或者是应该由编译器管理的东西。

转换是一个非常强大的工具,我们可以编写一个全新的转译器作为转换,但我认为这不是正确的方法。

是的,它之前至少已经提出过一次,为什么不考虑一下呢? TS 已经很少有实验性功能了

TypeScript 恰好有一个 3 年前添加的实验性标志,它是针对不再与当前提案兼容的 ECMAScript 提案。

Typescript 转译器在浏览器中生成错误的源代码

我理解您的期望,但鉴于编译器不会重写您编写的路径,我很惊讶您不认为自己代码的导入是“错误的”。 😉

对不起@DanielRosenwasser ,你听说过一个叫做 npm 的东西吗? 人们在那里张贴他们的包裹,以便我们所有人都可以使用它。 我了解您可能对我评价很高,但遗憾的是,我不是其中绝大多数的作者,因此我无法编辑它们添加扩展名。
如果我的项目只使用我的代码,我会更加感谢 typescript 带来的东西,因为它确实是我编码的右手(除了 WebStorm)。 然而,我正在使用 3rd 方包,就像我认为我们大多数人一样,那些不一定包含扩展。 一些项目,比如 rxjs,实际上是在等待 typescript 提供添加扩展的方法,否则就需要他们改变整个构建过程,所以我们又要花更多的时间在工具而不是产品上。
现在拜托,你能回答3个问题吗?

  1. 打字稿团队愿意提供这个功能吗?
  2. 如果是,你会接受PR吗?
  3. 如果没有,你打算什么时候发布这个?

如果第一个问题的答案是“否”,请关闭此问题,正式声明您不愿意发布此功能,如果它不来,请不要让其他人等待。

是的,它之前至少已经提出过一次,为什么不考虑一下呢? TS 已经很少有实验性功能了

TypeScript 恰好有一个 3 年前添加的实验性标志,它是针对不再与当前提案兼容的 ECMAScript 提案。

Typescript 转译器在浏览器中生成错误的源代码

我理解您的期望,但鉴于编译器不会重写您编写的路径,我很惊讶您不认为自己代码的导入是“错误的”。 😉

@DanielRosenwasser很好,我只是认为我自己的代码是正确的,因为阅读 Typescript 规范(https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#11.3)似乎是正确的。

我很确定你已经阅读了 ECMAScript 规范。 在规范中,没有确定模块是否应该以扩展名结尾。 在规范中,模块导入是使用 HostResolveImportedModule 算法解析的,但定义不明确。 问题不在于 ECMAScript 规范。 问题是浏览器解析模块,就好像规范中定义的 [ModuleRequest] 是资源的路径。

记住这一点,只需转到该语言的主页: https ://www.typescriptlang.org/。

在页面的页脚中,您可以阅读以下几行:

以 JavaScript 开始和结束

TypeScript 从今天数百万 JavaScript 开发人员所知道的相同语法和语义开始。 使用现有的 JavaScript 代码,合并流行的 JavaScript 库,并从 JavaScript 调用 TypeScript 代码。

TypeScript 编译为干净、简单的 JavaScript 代码,可在任何浏览器、Node.js 或任何支持 ECMAScript 3(或更新版本)的 JavaScript 引擎中运行。


您承诺在每个浏览器上运行的代码,但它似乎不是真的,这就是为什么这个问题会持续一年多。

正如@Draccoz指出的那样,我们只想知道你在处理这个问题。 但是在你的主页上读到一件事而在这个问题上读到相反的东西有点令人沮丧。

不,我们不愿意发布与此相关的任何内容,直到至少明确诸如 Node 中的 ES 互操作之类的事情以及首先从 npm 传输传递依赖项的合理策略。 如果您不使用 TypeScript,那么您在依赖管理和解析方面也会遇到同样的问题,所以我们想出一些 JS 生态系统可能独立开发的东西是没有意义的。 但即使这些问题得到解决,我也不能保证我们会在此期间做出任何改变。

您承诺在每个浏览器上运行的代码,但它似乎不是真的,这就是为什么这个问题会持续一年多。

我认为这种解释过于字面化了。 var fs = require('fs')在浏览器中运行时不起作用, HTMLDivElement没有在 Node 中定义, String.prototype.startsWith在旧浏览器上不起作用。 为了解决这个问题,人们在 TypeScript 之外制作了工具和库/polyfill,因为它们适用于更广泛的 JavaScript 生态系统。

所以你能关闭这个问题吗? 这对于等待TS 执行或声明您不执行的其他项目来说是一个障碍。 如果您不能只添加一个简单的标志,请关闭此问题并让其他人了解您的决定。

@DanielRosenwasser感谢您的快速回复,我真的很感激。 我认为这是一个明智的选择。

我认为这种解释过于字面化了。 var fs = require('fs') 在浏览器中运行时不起作用,HTMLDivElement 未在 Node 中定义,String.prototype.startsWith 在旧浏览器上不起作用。 为了解决这个问题,人们在 TypeScript 之外制作了工具和库/polyfill,因为它们适用于更广泛的 JavaScript 生态系统。

当然是,但是 ¿ 对 Typescript 一无所知的人还能想到什么? 我的解释过于直白,这一事实与此文本可能“混淆”任何对 Typescript 一无所知的人一样真实 😉。

也许您可以更新您的文档(https://www.typescriptlang.org/docs/handbook/modules.html)以反映实际行为。

@DanielRosenwasser再次感谢您的回复。 我一直在等待类似的东西一年。

我想要这个功能的原因是我可以看到我可以在没有任何构建工具的情况下创建什么样的 Web 应用程序。 在那之前,我可以使用我之前写的那个脚本。

对于像我这样正在寻找能够在浏览器中使用 ES 模块和 TypeScript 的解决方案的人,我找到了https://github.com/guybedford/es-module-shims。 在我们等待规范完成和浏览器实现时,它充当了包名称映射的一种 polyfill。 它解决了@QuantumInformation在使用 TypeScript(TS 编译器除外)创作简单的 Web 应用程序时不想使用任何构建工具(我的问题也是如此)的问题。

import 'knockout'

export class MyViewModel {
    greeting: KnockoutObservable<string>
    target: KnockoutObservable<string>
    constructor() {
        this.greeting = ko.observable('hello')
        this.target = ko.observable('world')
    }
}
<!DOCTYPE html>


md5-f28d4b503a1603c40bfeb342f341bfbe


<main>
    <span data-bind='text: `${greeting()} ${target()}`'></span>
    <script type='module-shim'>
        import 'knockout'
        import { MyViewModel } from 'index'
        ko.applyBindings(new MyViewModel())
    </script>
</main>

理论上,一旦浏览器支持包名称映射,您只需在 HTML 文件中用type='module'查找/替换type='module-shim' ,并将 packagemap 脚本更改为最终确定的任何内容,以便将 packagemap 包含在规格。

可能还值得注意的是,浏览器或任何东西都没有强制使用.js扩展名 - 您始终可以将您的网络服务器配置为更像unpkg -并提供.js文件无扩展的请求 URL。 这一切都可以在网络服务器端进行配置。

@weswigham虽然这可能是非常有问题的,因为 tsc (和经典节点模块解析)会知道./foo./foo.js引用同一个文件,但是浏览器会以不同的方式对待它们,即使网络服务器重定向。 您必须确保在每次导入时都以完全相同的方式引用文件。

顺便说一句,这个问题并没有阻止 TypeScript 生成的模块在今天的浏览器中使用。 只需始终导入带有.js扩展名的文件。 tsc 做正确的事情并从.ts文件中解析类型,并且您可以获得浏览器兼容性。

@weswigham请记住,在某些情况下您正在提供文件而无法访问网络服务器。 GitHub Pages、IPFS、S3 等。随着单页应用程序框架的出现,运行“无服务器”变得越来越普遍(这里的无服务器意味着没有您控制/配置为您的资产提供服务的服务器),所以您当请求apple $ 时,不能依赖服务器端过滤器来服务apple.js #$ 。

您必须确保在每次导入时都以完全相同的方式引用文件。

根据您的创作偏好,始终可以制作一个或另一个 404。 如果您在项目中使用符号链接,它们会导致类似的问题,您也需要选择如何处理。

总是导入带有 .js 扩展名的文件

是的,这也很好。

@QuantumInformation在此处使用的代码段是供公开使用的吗? 我可以在项目中使用它吗? 😃

@distante是的,您可以使用它

仅供参考,如果您使用 Jest 测试并更改源中的 .js 扩展名 - 它会中断
但是您可以将以下内容添加到您的 jest 配置中以解决该问题

  "jest": {
    ...
    "moduleNameMapper": {
      "(.*)\\.js": "$1"
    }
  }

你好@MrAntix

我认为 jest 是应该适应 TypeScript 的库,而不是相反。

我有另一个网站,我不想使用构建工具。 这是我们最擅长的:

https://github.com/microsoft/TypeScript/issues/16577#issuecomment -452312753

作为一种解决方法,您可以这样做(即使它是 ts 文件,而不是 js)😢

Screenshot 2019-06-05 at 22 47 49

如果有人想尝试一下:
https://github.com/QuantumInformation/web-gen-bot

但是,我无法让源代码调试与当前的 tsconfig 一起工作,将报告回来。

最后我回到 webpack,node_modules 是试图摆脱浏览器中构建工具的杀手。

不仅仅是 Jest——如果你在发布之前使用tsc编译 TypeScript,任何导入结果包的 JS 模块都会因为模块分辨率的差异而中断。

这里的讨论似乎已经分为两个相关但最终独立的问题:

  • Typescript 应该生成可以在浏览器中本地加载的文件
  • 打字稿应该解决节点模块路径并不总是有效的 URL 的事实。

我认为我们需要关注第一个问题,因为第二个问题显然是 typescript 无法自行解决的。

然而,第一个问题绝对是 Typescript 可以解决并且应该优先考虑的问题,因为对于大多数想要使用 Typescript 构建针对现代浏览器的 web 应用程序的人来说,它是一个主要障碍。

在我看来,问题归结为:

如果运行tsc生成一个带有.js扩展名的文件,然后生成另一个导入第一个.js文件的文件,则使用什么扩展名没有歧义,应该有一个包含扩展名的选项。

对于指向除 Typescript 生成的文件以外的文件的所有 import 语句,我认为保持这些不变是可以的(就像 Typescript 今天所做的那样)。

根据第一个选项,我认为命令行参数是最好的,默认情况下它会关闭。 我这样说是因为,如果我使用 webpack,我不希望将 .js 添加到导入中,因为 webpack 负责模块捆绑。

无论如何,node_modules 是阻止我使用本机模块浏览器应用程序的hack的杀手。

我这样说是因为,如果我使用 webpack,我不希望将 .js 添加到导入中,因为 webpack 负责模块捆绑。

Webpack 可以很好地处理.js扩展,所以没有区别。

事实上,有时需要将.js与 Webpack 一起使用(在有多个同名但扩展名不同的文件的情况下)。

那么你能解释一下为什么你不希望使用 Webpack 导入.js吗?

当我说I don't want时,我的意思是我不需要我的用例。

我没有看到一个令人信服的理由为什么它不应该成为默认行为(总是不愿意添加另一个配置标志......)

将导入设置为.js会导致 ts-node 停止对该文件正常工作。 这是因为 ts-node 一次只编译一个文件,如果发出的文件内容以require('./foo.js')结尾,那么当 nodejs 处理该文件时,它将尝试加载不存在的./foo.js磁盘上的任何位置,因此它将失败。 另一方面,当代码确实需要( ./foo )时,将调用 ts-node 的处理程序,此时它可以将./foo.ts编译为 JS 并返回。

如果 TypeScript 在所有情况下都发出.js扩展,那么 ts-node 将遇到同样的问题。 但是,如果有一个选项可以切换是否自动添加扩展,那么 ts-node 可以将该编译器选项设置为关闭,这将允许当前系统继续工作。

由于 Node.js --experimental-modules需要强制文件扩展名,因此 API 很简单,不需要依赖分析 - 像--jsext这样的选项可以将任何.ts扩展名重写为.js扩展名。 JSON / 其他格式的导入也可以正常工作,前提是用户已经明确包含了他们的所有扩展。 唯一有问题的情况是包名称以.ts结尾,例如import 'npmpkg.ts' 。 这种情况极为罕见,但为了在解决方案中全面处理它,规则可能是按照以下方式对 _bare specifiers_ 进行例外处理 - 如果裸说明符(不是 URL 或相对路径)是有效的 npm 包名称(匹配/^(@[-_\.a-zA-Z\d]+\/)?[-_\.a-zA-Z\d]+$/ ,来自 https://github.com/npm/validate-npm-package-name),然后忽略重写中的.ts扩展。

我创建了一个 TypeScript 编译器转换器,它将.js文件扩展名附加到任何相对导入路径上。 这意味着如果您的.ts文件有import { Foo } from './foo' ,它将发出import { Foo } from './foo.js' 。 它在 NPM 上可用,如何使用它的说明可以在项目自述文件中找到。

https://github.com/Zoltu/typescript-transformer-append-js-extension

如果将这样的东西集成到 TypeScript 编译器中(作为编译器选项),它应该更聪明地决定何时附加.js扩展名以及何时不附加。 现在它会查找以./../开头的任何路径,并且路径中的其他任何地方都没有. 。 这意味着它不会在许多边缘情况下做正确的事情,尽管我质疑是否有人_实际上_会遇到这些边缘情况。

@MicahZoltu很高兴看到用户态解决方案。 我认为尽管心智模型总是包含文件扩展名是非常重要的,这样 TypeScript 扩展名选项可以在 compile_ 上将 .ts 扩展名转换为 .js 扩展名。 这避免了解析边缘情况,只留下碰巧以“.ts”结尾的 npm 包名称的情况,这可以像我在之前的评论中讨论的那样处理。

@guybedford.ts文件中包含.js扩展名会使文件无法在 ts-node 中执行。 由于 NodeJS 进行文件解析的方式,解决 ts-node 中的问题绝非易事。 这意味着,如果您发布带有硬编码.js扩展的库,则该库将不适用于使用 ts-node 的任何人。 请参阅https://github.com/TypeStrong/ts-node/issues/783 上的讨论。

@MicahZoltu我的意思是在.ts文件中包含.ts扩展名。

@guybedford在 .ts 文件中包含 .js 扩展名使得该文件无法在 ts-node 中执行。

这也是为什么在节点和浏览器中自动执行 TypeScript 是一个坏主意的另一个原因。

@MicahZoltu我的意思是在 .ts 文件中包含 .ts 扩展名。

我认为这个问题在于它打破了.ts文件和.js / .d.ts对之间的等价性。 这意味着,如果您要导入使用当前项目编译的文件,则使用.ts ,并且如果将文件移动到其他项目,则需要将导入更改为使用.js .

这也意味着在没有变压器的情况下,输出无法在标准环境中工作。 鉴于无法从.tsconfig文件安装转换器,这意味着您始终必须使用自定义编译器。 对于使用.ts而不是.js的小好处,这是一个相当大的障碍。

这意味着,如果您要导入使用 .ts 编译的当前项目的文件,并且如果您将文件移动到不同的项目,则需要更改对它的导入以使用 .js。

外部/内部导入边界是明确定义的。 如果依赖项从当前相同构建的内部.ts文件变为另一个包导入的外部.js文件,该文件具有自己的单独构建,甚至可能不是 TypeScript ,那么是的,您必须更改扩展名,因为它是一个完全不同的概念。

这也意味着在没有变压器的情况下,输出无法在标准环境中工作。

虽然转换器可以帮助我们探索这个问题,但非常需要--ts-to-js或类似的编译器标志/选项来解决这个问题。

@guybedford您让我相信,为插件添加.ts是正确的做法。 然而,在尝试实现它时,我了解到 TypeScript 实际上并不允许它!

// foo.ts
export function foo() { console.log('foo') }
// bar.ts
import { foo } from './foo.ts' // Error: An import path cannot end with a '.ts' extension. Consider importing './foo' instead
foo()

你好,这个呢?


输入:

// src/lib.js.ts
export const result = 42;
// src/index.js.ts
import { result } from "./lib.js";

console.log(result);

输出:

// build/lib.js
export const result = 42;
// build/index.js
import { result } from "./lib.js";

console.log(result);

问题 #30076

TypeScript 文件上唯一的.ts扩展名对我来说最合适。 通常直到编译时你才知道目标是什么。 例如,在我的许多库项目中,我有多个针对不同环境的tsconfig.json文件,例如针对应该发出.js文件并将导入路径映射到.js的现代浏览器,或针对现代节点,它应该发出.mjs文件并将导入路径映射到.mjs

@MicahZoltu导入以.ts结尾的文件是一个错误实际上可能对我们有利。 因为这意味着只要使用.ts扩展而不需要标志,就可以在编译时添加.ts.js扩展重写:)

也许一个 PR 来启用尾随.ts扩展被支持并重写为.js扩展(可能在一个标志下启用此功能,但一个可以及时删除的标志)将是一个好方法沿着这条路开始。

//cc @DanielRosenwasser

这个问题已经很久了,所以有人可能已经说过了,所以冒着重复已经说过的话的风险:

TypeScript 应该允许开发人员在import中指定扩展,因为它是有效的 JavaScript。 这不仅仅是关于 JS/TS 扩展! 在 ESM 中,只要 MIME 类型正确,任何扩展都是有效的。

import actuallyCode from './lookLikeAnImage.png';

…只要服务器在该文件中提供有效的 JavaScript 并设置正确的 MIME 类型,就应该是有效的。 这更进一步,因为 TS 可能也是如此! TS 文件可能只是一个 JS MIME 类型的 JS 源代码,因此是 ESM 模块导入的有效路径。

IMO TypeScript 应该只考虑不带扩展名的导入(就像它今天想要的那样),并让那些单独存在,没有错误、警告等。否则它会拒绝有效的 JavaScript 代码,这对于声称是 JS 超集的东西来说是非常不幸的。

抱歉,如果这不是提出这个问题的正确地方,我看到了其他一些问题:#18971、#16640、#16640 但是关于这个主题的问题似乎正在左右关闭,所以我认为这是“主要”问题因为它被允许保持开放。

玩 NodeJS v12.7.0

salathiel@salathiel-genese-pc:~/${PATH_TO_PROJECT}$ node --experimental-modules dist/spec/src/ioc
(node:15907) ExperimentalWarning: The ESM module loader is experimental.
internal/modules/esm/default_resolve.js:59
  let url = moduleWrapResolve(specifier, parentURL);
            ^

Error: Cannot find module '${PROJECT_ROOT}/dist/spec/src/ioc' imported from ${PROJECT_ROOT}/
    at Loader.resolve [as _resolve] (internal/modules/esm/default_resolve.js:59:13)
    at Loader.resolve (internal/modules/esm/loader.js:73:33)
    at Loader.getModuleJob (internal/modules/esm/loader.js:149:40)
    at Loader.import (internal/modules/esm/loader.js:133:28)
    at internal/modules/cjs/loader.js:830:27
    at processTicksAndRejections (internal/process/task_queues.js:85:5) {
  code: 'ERR_MODULE_NOT_FOUND'
}
salathiel@salathiel-genese-pc:~/${PATH_TO_PROJECT}$ node --experimental-modules dist/spec/src/ioc.js
(node:16155) ExperimentalWarning: The ESM module loader is experimental.
internal/modules/esm/default_resolve.js:59
  let url = moduleWrapResolve(specifier, parentURL);
            ^

Error: Cannot find module '${PROJECT_ROOT}/dist/spec/src/observe' imported from ${PROJECT_ROOT}/dist/spec/src/ioc.js
    at Loader.resolve [as _resolve] (internal/modules/esm/default_resolve.js:59:13)
    at Loader.resolve (internal/modules/esm/loader.js:73:33)
    at Loader.getModuleJob (internal/modules/esm/loader.js:149:40)
    at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:43:40)
    at link (internal/modules/esm/module_job.js:42:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}

现在,我不知道为什么这很烦人......

目前 TypeScript 不会重写路径。 这肯定很烦人,但您目前可以自己添加.js扩展。

@DanielRosenwasser https://github.com/microsoft/TypeScript/issues/16577#issuecomment -309169829

选项背后的实施怎么样? 喜欢--rewrite-paths ( rewritePaths: true ) 吗?

@viT-1 这是暂时的解决方法: https ://github.com/microsoft/TypeScript/issues/16577#issuecomment -507504210

@MicahZoltu我只是通过 SystemJS 捆绑解决了我的用例(tsconfig outFile 选项)

我还没有阅读人们对此发表的所有评论,但我不明白为什么我必须写.js无数次。 我想让电脑为我做这件事。

@richardkazuomiller我们都这样做,但@DanielRosenwasserhttps://github.com/microsoft/TypeScript/issues/16577#issuecomment -448747209 中表示他们现在不愿意这样做(我很惊讶这个问题仍然存在误导性在上述声明之后这么长时间)。 如果您希望计算机为您执行此操作,请考虑使用捆绑程序或某些 3rd 方工具来处理导入路径重写。

谁要我关闭这个问题?

请不要关闭,我仍在等待 TypeScript 团队如何回应他们将如何处理允许使用任何扩展并依赖 MIME 类型的 ESM 导入。 目前这在 JavaScript 中是可能的,但在 TypeScript 中是不可能的。 (详情请看我之前的评论。)

@TomasHubelbauer这就是我在配置文件中提出一个标志的原因,它可能表明所有无扩展名的导入都应该添加 .js (或任何其他配置的)扩展名。 这将是可选的,因此默认值可能为 false,从而使现有项目或具有不同需求的项目按原样工作。

如前所述:

不确定是哪个版本的 TS 添加了它,但现在可以使用 ' ./file.js'之类的导入(即使文件实际上是 file.ts)。
TypeScript 可以很好地解析文件,并将完整的.js导入输出到目标。

这是有道理的,因为任何有效的 JavaScript 都是有效的 TypeScript。 因为你可以这样做:

import foo from './bar.js'

...在 JS 中,您也应该能够在 TS 中做到这一点。 如果你这样做,你就解决了使用原生 ES6 模块的问题,因为你的扩展是正确的。


我们也不要忘记,当浏览器看到./foo/bar的导入时,它实际上会发出请求。 没有提供任何服务的事实是由于服务器。 您可以通过对/foo/bar的请求满足/foo/bar.js的方式对其进行配置。 并不是说这是一个好的解决方案,甚至是一个好主意,但它在技术上是可行的。

配置文件中的标志,它可以指示所有无扩展名的导入都应该添加 .js(或任何其他配置的)扩展名

这将解决绝大多数情况。 TypeScript 团队愿意为此配置选项考虑 PR 吗?

NodeJS 现在_默认_在相对路径模块解析方面的行为方式与浏览器相同。 默认情况下,它将不再推断.js扩展名。 这意味着 TSC 的当前行为导致发出在所有运行时上下文中无效的 JS。 感觉这个问题现在应该得到解决,因为 ESM 的浏览器和 NodeJS 行为都很明确。

https://nodejs.org/api/esm.html#esm_customizing_esm_specifier_resolution_algorithm

esm 的新解析器需要新的解析模式 - moduleResolution: node实际上只是我现在可以称之为“旧版本节点”的 cjs 解析器。 如果您打算以新的 esm 解析器为目标,我们将需要一个新的解析模式来更恰当地匹配。 特别是因为节点仍然完全支持所有现有配置中的旧解析器。 (请注意:制作这样一个新的解析器_我们的默认_会很困难)

尽管! 虽然对此存在_不同意_并且_有些人会选择将意见陈述为事实_,但仍有一个--es-module-specifier-resolution=node标志返回预期的扩展行为,并且可能仍会成为默认值 - 正如文档所说,尽管没有标记,节点中的 es 模块_在本质上仍然是实验性的_。

另外,作为对原始请求的提醒:我们不重写导入。 曾经。 完全没有。 说明符输入 === 说明符输出。 说明符描述了结构意图,我们的目标不是隐式地从一个预期结构映射到另一个结构。 你也可以这样想:如果你写了const varFoo = "./Foo"; import(varFoo) ,如果需要,我们会以某种方式添加扩展名吗? 不——这太荒谬了——为了处理所有形式的动态输入,我们需要在运行时包装动态导入,突然间我们变成了我们自己的模块加载器,位于内置平台加载器之上。 相反,我们将提供一个解析器模式,当您省略扩展名时会适当地出错(毫无疑问,您已经可以找到 lint 规则)。

如果您发现此线程并且您_真的希望_您的输入源没有扩展,并且您_真的需要_您的输出在定位 (node es) 模块时仍然可以使用,您应该在https://github.com/上提供反馈

突然间我们变成了我们自己的模块加载器

无论如何, tsc不是必须是功能模块加载器吗? 如果找不到导入的模块,则无法对导入进行类型检查。 我只想看到所有各种模块加载器都以广泛兼容的方式运行,或者至少以可预见的不同方式运行,这种方式相当容易适应。

在相对路径模块解析方面,NodeJS 现在默认的行为方式与浏览器相同。 默认情况下,它将不再推断 .js 扩展名。 这意味着 TSC 的当前行为导致发出在所有运行时上下文中无效的 JS。

这难道不是在 TypeScript 已经支持的说明符中使用.js文件扩展名的另一个原因吗? 这解决了所有问题,除了如果tsc在无扩展导入时出错会很好。

无论如何 tsc 都不是功能模块加载器吗? 如果找不到导入的模块,则无法对导入进行类型检查。 我只想看到所有各种模块加载器都以广泛兼容的方式运行,或者至少以可预见的不同方式运行,这种方式相当容易适应。

我们的目标不是提供运行时加载器实现——只是一个反映您所针对的任何运行时加载器的编译时分析。 如果没有运行时组件,我们不可能重新映射所有导入,而我们没有,也没有打算拥有。

无论如何 tsc 都不是功能模块加载器吗?

不是在运行时。 tsc需要了解选择的运行时将如何解析说明符并在构建时实现它,但运行时完全由环境决定。

你也可以这样想:如果你写了 const varFoo = "./Foo"; import(varFoo) 如果需要,我们会以某种方式添加扩展名吗?

@weswigham这是关于动态导入的有力论据,但我不_认为_您可以使用静态导入来做到这一点? 我可以理解策略应该对两者相同的论点,但我认为值得一提的是,静态导入与动态导入的作用非常不同,我认为对静态有不同的策略并不是完全_unreasonable_导入重写和动态导入重写。

这难道不是在 TypeScript 已经支持的说明符中使用 .js 文件扩展名的另一个原因吗? 这解决了所有问题,但如果 tsc 在无扩展导入时出错,那就太好了。

@justinfagnani有两个问题:

  1. 如果您在导入时指定.js扩展,则本机执行 TS 的运行时(例如 TS-Node)将无法工作。
  2. 如果您在导入中包含 .js 扩展名,您就是在对编译器 (IMO) 撒谎。

关于(2),如果我有两个 TS 文件a.tsb.tsa.ts确实import ... from './b.js' ,我告诉编译器“我有一个兄弟文件命名为b.js 。这个陈述是不正确的。更糟糕的是,如果你同时拥有.b.tsb.js (它们实际上可能不是彼此的衍生物),它现在变得模棱两可,你指的是哪个_即使你明确地包含了一个扩展_。

我认为用户应该告诉编译器他们真正想要什么(作为用户),那就是“导入任何名称为 X 和任何扩展名的文件”(在import ... from './foo'的情况下) )或“专门导入此文件”(在import ... from './foo.ext'的情况下)。 如果编译器检测到您正在导入 TS 文件,并且编译器继续发出.js文件,那么我相信编译器应该更新导入以正确导入适当的文件。 这可能意味着在 import 语句中将.ts更改为.js

@justinfagnani有两个问题:

  1. 如果您在导入中指定 .js 扩展名,则本机执行 TS 的运行时(例如 TS-Node)将无法工作。

这是 TS-Node 中的一个错误。 它与tsc的行为不符。

  1. 如果您在导入中包含 .js 扩展名,您就是在对编译器 (IMO) 撒谎。

它实际上是相反的——你说的是实际进口的真相。 您将_不会_导入.ts文件,而是导入 $# .js文件。 .d.ts / .js对和.ts文件之间也应该没有明显的区别。 它们是可以互换的。 完成此操作的唯一正确方法是导入.js文件 - 编译后.ts文件将是什么。

@justinfagnani

.d.ts/.js 对和 .ts 文件之间也应该没有明显的区别

这并非总是如此。 您可以将您的服务器配置为使用 JS MIME 类型为所有 JS、TS 和 DTS 提供服务,然后在 JavaScript 中分别导入它们,运行时将尽职尽责地作为 JS 执行它们。 ESM 根本不关心扩展。

这就是为什么我认为 TS 用户实际上应该被迫包含一个.ts扩展名,除非实际导入一个无扩展名文件或该名称,否则无扩展名将是一个错误。 并且 TS 会在输出中将导入重写为.js 。 如果存在 JS 文件(不是由 TS 生成的),那也将是一个错误。

这是 TS-Node 中的一个错误。 它与 tsc 的行为不匹配。

这是 NodeJS 加载程序的限制,而不是 TS-Node 中的错误: https ://github.com/TypeStrong/ts-node/issues/783#issuecomment -507437929

这并非总是如此。 您可以将您的服务器配置为使用 JS MIME 类型为所有 JS、TS 和 DTS 提供服务,然后在 JavaScript 中分别导入它们,运行时将尽职尽责地作为 JS 执行它们。 ESM 根本不关心扩展。

我很清楚这一点,但浏览器也不会执行 TypeScript。 环境实际加载的文件是 JavaScript 文件,这几乎是普遍事实。

我的观点是,如果你已经有一对.js / .d.ts对,比如从第 3 方包中导入.js文件, tsc将查看.d.ts文件并加载类型。 即, .js / .d.ts对 _acts_ 就像.ts文件,这就是它应该的样子,因为这就是您可以透明地从 JavaScript 移植到 TypeScript 而无需更改的方式被移植的文件的导入。

这是 TS-Node 中的一个错误。 它与 tsc 的行为不匹配。

这是 NodeJS 加载器的限制,而不是 TS-Node 中的错误:TypeStrong/ts-node#783(评论)

TS-Node 与tsc行为不同仍然是一个错误。 这使得.ts文件在tsc和 TS-Node 中都不起作用,我强烈认为tsc作为 TS-Node 应该遵循的标准制定者.

我的观点是,如果你已经有一个 .js/.d.ts 对,比如从第 3 方包中导入 .js 文件,tsc 将看到 .d.ts 文件并加载类型。 即,一个 .js/.d.ts 对就像一个 .ts 文件,这就是它应该的样子,因为这是您可以透明地从 JavaScript 移植到 TypeScript 的方式,而无需更改所移植文件的导入。

如果您加载一个外部_package_,那么您将需要某种类型的运行时包加载器(例如,浏览器中的命名导入映射),它将命名模块映射到入口点文件。 但是,我认为您的观点通常是站得住脚的,在您有一个相对的 .js/.d.ts 对的情况下。 在这种情况下,您是否应该做import ... from './foo.d.ts'import ... from './foo.js' ,我持观望态度。

.d.ts文件本质上是一个描述结构的标头,它不包含有关它映射的扩展名的信息(假设通常你不应该在同一个地方有多个具有相同名称但不同扩展名的文件,并且如果你这样做,我们会在构建时出错) - 即使在今天,相关的运行时“源”也可能是.js.jsx (使用jsx: preserve )。 您不能在发射时将导入中的.d.ts引用替换.js并假设它有效......

重要的是 tsc 对于更广泛的问题不要太固执己见
构建和解决假设。

鉴于它是复杂边界上的多功能工具,因此可以预期
这种配置可以让它按照用户想要的方式行事。

一直在刻意努力不将分辨率更改暴露为
tsc 编译过程的一部分——这本身就迫使人们对
用户并在他们的工作流程中造成摩擦。

2019 年 11 月 27 日星期三 16:48 Wesley Wigham [email protected]
写道:

.d.ts 文件本质上是一个描述结构的标头,其中包含
没有关于它映射的扩展名的信息(假设是
通常你不应该有多个同名的文件,但是
不同的扩展名在同一个地方)——即使在今天,相关的
运行时“源”可以是 .js 或 .jsx(使用 jsx:preserve)。 你可以
不是,广泛地,在发射时将 .d.ts 引用替换为 .js 的导入
并假设它有效......


你收到这个是因为你被提到了。
直接回复此邮件,在 GitHub 上查看
https://github.com/microsoft/TypeScript/issues/16577?email_source=notifications&email_token=AAESFSQS2DQ23RR5KN3RTZ3QV3TLXA5CNFSM4DPRQTY2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEFK2TPA#issuecomment-5592621
或退订
https://github.com/notifications/unsubscribe-auth/AAESFSUAP2YO23ZFHCOWVQLQV3TLXANCNFSM4DPRQTYQ
.

不知道如何处理这种情况,但对同一件事有疑问。

我的 html 文件中有这样的导入映射:

<script type="importmap-shim">
      {
        "imports": {
          "@root/":"../../../",
         }
      }
</script>

我有我的ts文件,我正在导入这样的文件:

import '@root/components/page-main/page-main.js';

现在,此设置在浏览器中运行良好。 但是,我将如何在 VSCode 中导航/使用它? 我的意思是,我想 ctrl+单击导入并导航到文件,获取自动完成,键入 def 等。

另外,考虑到 tsc 不会将导入扩展从 .ts 重写为 .js ,是否有任何其他推荐的工具/包我可以在构建时使用它来做同样的事情? 谢谢。

(我将 https://github.com/guybedford/es-module-shims 用于 ES 模块和导入地图)

但是,我将如何在 VSCode 中导航/使用它?

查找paths编译器选项。

另外,考虑到 tsc 不会将导入扩展从 .ts 重写为 .js ,是否有任何其他推荐的工具/包我可以在构建时使用它来做同样的事情? 谢谢。

只需使用.js就可以正常工作,即使您在构建时引用.ts文件也是如此。

@tvvignesh https://github.com/Zoltu/typescript-transformer-append-js-extension/用于在编译时将无扩展名导入重写为 .js。 我个人不喜欢“只需将 .js 放入您的 TS 文件”,因为如果您这样做,您的代码将不会在 ts-node 中运行。 如果您不需要在 ts-node 中运行代码,那么使用 .js 即可。

但是,我将如何在 VSCode 中导航/使用它?

查找paths编译器选项。

另外,考虑到 tsc 不会将导入扩展从 .ts 重写为 .js ,是否有任何其他推荐的工具/包我可以在构建时使用它来做同样的事情? 谢谢。

只需使用.js就可以正常工作,即使您在构建时引用.ts文件也是如此。

感谢您的快速回复。 我已经在 tsconfig.json 中设置了路径选项,但仍然无法通过 ctrl+click 进行导航。

这是我在 tsconfig.json 中的路径选项

"paths": {
            "*": ["www/node_modules/*"],
            "@modules/*": ["www/node_modules/*"],
            "@root/*": ["www/*"]
        }

只需使用.js就可以正常工作,即使您在构建时引用.ts文件也是如此。

当有数千个进口时,这是非常不切实际的。

“只使用.js ”的问题在于,如果 TypeScript 发出.mjs文件,那么您的程序将无法运行。 这意味着您最终会遇到这样一种情况:您正在编写 TypeScript,_要么_ 在浏览器中工作_要么_ 在 NodeJS 中工作。 您不再可以编写同时适用于两者的 TypeScript。

我相信这里的论点是,“这不是 TypeScript 的错,那是浏览器和 NodeJS 的分歧”。

Node 13.2 很好地支持 .js 扩展。

@justinfagnani对于 ES 模块? 我认为 NodeJS 只会加载具有.mjs扩展名的 ES 模块,否则文件被视为 CommonJS?

我觉得这整个线程有点误导。 显式编写文件扩展名将解决整个问题。 如果要导入 JavaScript 文件,请编写 .js 扩展名。 如果要导入 TypeScript 文件,请编写 .ts 扩展名。 这在标准中是允许的,TypeScript 编译器同样能很好地理解它。 唯一的问题是编译器声明 .ts 扩展名是错误的。 这不会影响纯转译,而只是类型错误。 TypeScript 应该解决这个问题,因为显然 .ts 文件扩展名是有效的,因为我们是在 TypeScript 中创作的。 为了解决这个问题,Deno.js 有一个 VS Code 扩展: https ://marketplace.visualstudio.com/items?itemName=justjavac.vscode-deno

随着 WebAssembly 的出现,Web 应用程序将开始导入越来越多的文件类型。 如果要导入 .wasm、.js、.ts、.rs、.c 等文件,明确说明将变得越来越重要

有人真的用过我写的脚本吗?

https://github.com/microsoft/TypeScript/issues/16577#issuecomment -310426634

如果有任何反馈?

@QuantumInformation我没有用过你的脚本,但是我写的 TypeScript 转换器插件基本上做同样的事情。 😊 https://github.com/Zoltu/typescript-transformer-append-js-extension/

首先,我不会再尝试说服 typescript 来实现它,我明白了,你不会那样做。
我正在阅读评论并想知道:所有那些说将.js添加到路径将解决所有问题的人 - 你真的不使用 3rd 方包吗? 我如何为我的导入添加扩展来修复 node_modules 中的内容?

@MicahZoltu哦,不错

你的意思是? 如果使用路径,您将显式编写从 node_modules 导入的文件的文件扩展名。 但这通常不是一个简单的进口吗?

如果您指定相对路径,它将不会是裸露的。 然而,我正在谈论我正在导入的包的内部导入,例如查看 rxjs。

@Draccoz我认为,如果有人编写像import { ... } from './foo'这样的 TypeScript,然后将其作为 NPM 包发布,那么即使它以 ES 模块为目标,用户也不能直接在浏览器中使用该库。 我相信微软在这里的论点是,如果它分发这样的代码并期望它在浏览器中工作,那么这个包是错误的。

您将编写存在于 node_modules 中的文件的扩展名。 import * as stuff from 'rxjs/path/to/file.js';如果是 JavaScript 文件, import * as stuff from 'rxjs/path/to/file.ts';如果他们分发了 TypeScript 文件。 如果我没记错的话,这现在有效

@lastmjs是的,但是如果rxjs/path/to/file.ts包含import foo from './bar'怎么办? 您无法更改它,因为您无法修改文件,因为它是第三方库。

这就是为什么我们应该推动在任何地方使用显式扩展,但是是的,我明白你的意思。 我的工具(https://github.com/lastmjs/zwitterion)将此重写作为那些没有扩展名的文件的权宜之计。

是的,我的意思就是——仅仅添加扩展并不能修复我们无法修改的第三方库,所以这不是解决方案。
我也得到了微软的观点,因此我不再推动它(除了保持这个问题开放会带来不必要的希望,这会阻止包维护者只是调整构建而不是等待 TS 解决这个问题),它会虽然只有打字稿作为构建工具而没有其他依赖项,但还是很棒的。

是的,但显然希望对很多人来说仍然是好事

image

尽管如此,通过这些评论已经多次声明它将不会实施,那么为什么要保持开放呢?

如果有人编写 TypeScript,例如 import { ... } from './foo' 然后将其发布为 NPM 包

很多人在 NPM 包中发布未编译的 TS 吗? 我正在寻找这个供我自己的内部使用。 我正在制作项目之间共享的公共代码库,并且认为 NPM 是一种打包它们的合理方式,我们的团队已经完全转向 TS,所以我很乐意避免编译这些 deps。 但看起来最常见的用例是使用单独的类型将包构建到 JS,然后将modulesmain指向 JS,将types指向类型。

是否有将未编译的 TS 发布到 NPM 的约定? 如果是这样,这是否记录在某处? 我知道这与原始问题的 OT 无关,但我认为它与答案相关,因为我们需要知道“第三方 TypeScript”(包)是否是受支持的用例/目标。

@thw0rted抱歉不清楚,我的意思是:

...如果有人像import { ... } from './foo'这样编写 TypeScript,然后使用 TSC 将其转换为 JS,并将该 JS 作为 NPM 包发布...

如果它不会实施,我将把它留给 MS 现在关闭问题。

啊,我现在明白了。 是的,当我编译(使用target: ES2018 )时,发出的 JS 将扩展名从 import 语句中移除,但它对我有用,因为消费者正在通过 webpack/babel 放置所有内容,并且它必须填写缺少的扩展名为了我。 我现在跟上速度了。 我什至没有想到,如果不先通过 webpack,我的“包”将无法在浏览器中本地运行。

是的,这是一个艰难的过程。

@thw0rted我不使用 webpack/babel,因此从盒子中解析 .js 对我来说是个问题。
一些解决方案/解决方法是为默认解析源配置 Web 服务器。

抱歉,如果我重复其他人所说的话,但这是我每天都在处理的事情,所以我想再投入我不请自来的两美分。

我知道 TypeScript 不必迎合任何特定环境(Node.js、Webpack 等),但事实是有很多环境总是需要扩展,除非另有特殊配置,所以我不这样做t认为他们应该被忽略。 导入不再位于 Node 中的标志后面,并且导入路径必须与 Web 上的 URL 路径名匹配。 虽然在 URL 的结尾处没有.js的需要,但这是事实上的标准。

如果微软的好人真的希望没有文件扩展名是默认的,我不会争论。 你可能比我聪明,但如果不是根据同一文件中的其他导入来推断是否需要扩展,我肯定会更高兴,这可以在项目范围内设置,以便它首先适用于 Intellisense 自动完成导入添加到文件!

同时,有没有人知道任何解决此问题的 VSCode 插件? 向 eslint 添加导入/扩展是我能做的最好的处理。

目前我编写了没有 .js 扩展名的import行,然后使用sed在输出文件中添加 .js 扩展名。
https://github.com/yoursunny/NDNts/blob/9f50fcec245b33c7649fa815bbb3dd404eee160e/mk/build.sh#L12 -L14
我必须在生产构建期间删除 SourceMap,因为在修改输出文件后它们将不再匹配。

我无法在 .ts 源文件中编写 .js 扩展名,因为它破坏了ts-jest
虽然可以在已编译的 .js 文件上运行 Jest,但它会破坏Coveralls

@yoursunny是的,sed 是变体(我也使用过它,但更喜欢替换文件,因为可以通过 importmap 文件映射配置的配置)替换生成的 js 文件中的字符串,但它闻起来不太好=)可能是对于带有转换插件的 ttypescript(例如,@MicahZoltu 的 typescript -transform-paths ),添加扩展将是有用的功能/选项。

@richardkazuomiller这部分不是真的:

如果微软的好人真的希望没有文件扩展名是默认的

仅打字稿:

  1. 允许 Node-require-style 模块解析(他们应该扩展它以允许 Node-import-style 解析,这需要文件扩展名)。
  2. 在同一个编译单元中,甚至在编译器生成它们之前,就将 .js 文件解析为 .js/.d.ts 对。
  3. 永远不会修改导入说明符。

所以解决这一切的方法是简单地导入带有.js扩展名的 .js 文件。 TypeScript 将解析到正确的 .ts 文件,并且不会修改导入说明符,因此一切只会在浏览器和 Node >= 13.2 中工作。

@yoursunny您是否针对ts-jest提交了错误? 它们与这里事实上的 TypeScript 标准不同。 如果不能在 .js 文件上使用源映射,这似乎也是工作服中的一个错误。

您是否针对ts-jest提交了错误? 它们与这里事实上的 TypeScript 标准不同。

不,因为我不完全理解 ts-jest 分辨率是如何工作的。

如果不能在 .js 文件上使用源映射,这似乎也是工作服中的一个错误。

Coveralls 从我的构建过程中接收 lcov 报告,然后使用提交到 GitHub 的文件显示代码覆盖率。
我不会将输出 .js 文件提交到 GitHub。 如果我对 .js 文件运行单元测试,lcov 报告将引用 GitHub 上不存在的 .js 文件。
因此,Coveralls 无法找到源文件。 它将显示覆盖百分比,但无法显示未覆盖的行。

新时代来了!

新发布的 Node.js v13 和所有主流浏览器都支持开箱即用的原生 ES 模块,TypeScript 以一种简单的方式支持这些环境会很棒。

对于最基本的用例(通过简单的静态文件服务器提供服务的网站),最简单的解决方案是添加一个新的编译器选项,如"appendJsExtension": true或类似的。

@mjbvz您关闭的 VS Code 仓库中的问题略有不同:它是关于使用自动完成自动添加导入语句; 当 VS Code 这样做时,它会添加没有.js扩展名的 import 语句,并且很容易忽略这一点,直到运行时失败。

但是,如果 TypeScript 添加了"appendJsExtension": true之类的选项或类似的选项,那么这并不重要,我们可以在源代码中没有.js的情况下编写 TS 代码。

也许 VS Code 插件应该有一个选项来启用/禁用在自动完成的导入语句中自动添加.js扩展?

@mjbvz VS Code / Intellisense 问题是相关的,但不应作为欺骗关闭。 如果这个问题最终以 WONTFIX 的形式关闭,那实际上会增加 VS Code 问题的重要性。

我看到有一点被忽视,并且不理解JS扩展名特殊的解决方案将如何解决这个问题:对于ESM,没有扩展名具有特殊状态,即使无扩展名文件也完全可以,只要服务器将它们提供给浏览器适当的文本/javascript MIME 类型。 这意味着代码如:

import something from './javascript-code.png';
import something2 from './javascript-code2.js';
import something3 from './javascript-code3.ts';

只要文件(无论其扩展名如何)包含 JavaScript 代码并以 JavaScript MIME 类型提供给浏览器,这一切都是有效的。

由于 TypeScript 应该是 JavaScript 的类型安全超集,因此我看不到允许像上面那样导入的方法,因为代码是有效的 JavaScript,如果 TypeScript 不接受它,它就不再是安全的超集JavaScript,是不是正确的?

那么隐含 JS 扩展或唯一允许的解决方案我认为不能解决这个问题? 此外,即使是 TS 和 DTS 扩展 IMO 也不能是特殊情况,因为虽然通常情况并非如此,但它们可以包含 JavaScript 代码,而不是 TypeScript 代码和服务器到浏览器本身,就 ESM 而言完全没有问题,因为它们使用正确的 MIME 类型。

我在这方面遗漏了什么吗? 鉴于file.tsfile (没有扩展)会导致冲突吗?

Web 和 Service Workers 也获得了对 ES Module import/export 的支持

我完全支持并赞同@trusktr将他们的想法实现为编译器选项的想法。 这并不是说我想摆脱 webpack 和 rollup 之类的打包工具。 但是我确实相信这样的功能将消除设置捆绑程序所花费的大量麻烦和时间,而您只想编译一个简单的 Typescript 项目而无需跳过箍或使用笨拙的解决方法。

请将此视为 Typescript 编译器的一个选项。 <3

有谁知道使用任何模块系统可以在没有 webpack 或--outFile的浏览器中运行已编译的单个 js 文件的方法?

我的问题是:我正在使用的相当大的 TypeScript 项目需要大约 35 秒才能使用 webpack 编译成使用ts-loader的捆绑 js 文件。
我可以优化它的唯一方法是使用transpileOnly将编译时间缩短到 20 秒,但这仍然很慢,而且我放松了类型检查。
摆脱 webpack 我可以使用 outFile 达到大约 14 秒,但这对我来说仍然太慢了。 使用incremental没有任何区别。

我可以获得几秒钟编译时间的唯一方法是每 1 个 ts 文件发出 1 个 js 文件,并使用incremental标志。 在这种情况下,我想 ts 只检测和编译真正改变的文件。 问题是我无法在我尝试过的任何模块目标的浏览器中运行它:system、amd、es6。
我在tsconfig.json和编译的 js 文件中使用paths映射,我得到的错误主要与无法解析这些别名有关。

有什么办法可以做到这一点?

@andrewvarga我目前直接在浏览器中运行我所有编译的 TypeScript,而无需捆绑或编译到单个文件。 最简单的方法是:

  1. 确保 TypeScript 文件中的所有导入都具有.js扩展名。
  2. 如有必要,运行一个开发服务器来解析依赖项的 npm 包名称的导入。 es-dev-server是我所知道的最简单的。
  3. 运行tsc --watch (确保模块在您的 tsconfig 中为esnext

而已。 如果您只是在 TypeScript 中为导入编写.js扩展名,则不需要其他工具来修复浏览器的文件。

@justinfagnani伙计,您忘记了最重要的信息——类型必须是 jsdoc 格式,任何基于原生 JavaScript 的打字稿抽象都会导致解析器失败。

@Draccoz tsc将输出 JavaScript,而不是 TypeScript。

@andrewvarga我所知道的在不捆绑的情况下进行编译的最简单方法是我的项目Zwitterion 。 它旨在替代静态文件服务器,因此您不必改变开发方式。 即使在脚本元素中,您也可以使用显式文件扩展名(用于 JavaScript 的 .js 和用于 TypeScript 的 .ts)导入和导出。 它非常高效,在文件更改时进行缓存和自动重新加载。 不过,您将不得不依靠您的编辑器进行静态分析(类型错误)

@andrewvarga我使用https://github.com/Zoltu/typescript-transformer-append-js-extension/并针对在所有现代浏览器中加载的本机 ES 模块(Safari 和新的 IE 除外)。

如果您的运行时 NPM 依赖项非常有限,您可以使用 es-modules-shim 加载它们而无需任何捆绑。 你可以看到我创建的一个反应模板,它显示了所有这些一起工作: https ://github.com/Zoltu/react-es2015-template

@MicahZoltu Safari 确实支持 ES 模块,我知道事实上,基于 caniuse 和个人经验: https ://caniuse.com/#search =modules

@justinfagnani awww 抱歉,错过了“编译的 TypeScript”部分,以为您在谈论 jsdoc 打字稿。

谢谢大家的各种提示!

我发现通过在 webpack 中使用hard-source-webpack-plugin npm 模块和在 ts-loader 选项中使用transpileOnly: true可以将构建时间缩短到大约 4-5 秒。
这当然会忽略打字稿错误,因此它仅对快速迭代构建、在浏览器中尝试以及依赖 IDE 来解决任何潜在错误有用,但我认为它在开发过程中非常有用。

为了同时获得 TS 错误和快速编译,我仍然认为唯一的方法是在浏览器中运行模块,但缺乏对 import-maps 和 js 扩展的支持使得这变得困难。
@justinfagnani @MicahZoltu谢谢,我会尝试这些选项。 一般来说,我宁愿避免使用更多的 npm 模块,但这似乎是不可避免的。

我也尝试使用 systemjs 使其工作,但我被导入地图卡住了..

@andrewvarga您可以通过我的带有 importmap 的简单示例尝试 SystemJs。
您也可以尝试我的重示例- systemjs 和 esm 替代方案,但是本机不支持 esm 导入映射,我们应该手动解析模块(gulp-replace),或者您可以尝试es-module-shims

因此,如果我使用tsc作为我唯一的编译器,则将esnextes2015作为我的模块类型。 有什么办法可以直接在浏览器中使用输出?

https://github.com/alshdavid-sandbox/typescript-only-compiler

如果我正确理解了该线程的其余部分,那么如果您将此行更改为from "./module.js" ,Typescript 应该非常乐意编译您的代码。 然后编译后的index.js将有一个有效的 ES6 导入语句,它应该都可以工作。

那是除非您导入一些不知道如何进行 ES 导入或不关心的库:P。

我为 Gulp 创建了一个插件,它添加了.js来导入文件路径。 (也是额外的 ts 路径分辨率)。

我使用 Gulp 是因为我不知道有任何其他任务运行程序可以让我基本上将一个简单的后处理步骤附加到 tsc 编译器(监视模式和正常模式)。 Webpack 和 rollup 都专门捆绑,我想发出 es 模块。

它有效,但看起来很慢,因为它似乎重建了整个事情。

https://github.com/alshdavid-sandbox/typescript-only-compiler/tree/gulp

随着节点团队决定反对无扩展导入,这一点变得越来越重要
https://github.com/nodejs/modules/issues/444

添加导入映射也决定反对无扩展导入
https://github.com/WICG/import-maps/issues/194

如果我们想在浏览器或节点中使用 typescript 编译的 es 模块,我们需要通过 typescript 添加扩展吗? (用于内部模块)和手动用于外部模块? @weswigham

再次@chyzwar ,如果您只是在 TypeScript 源代码中编写.js扩展名,那么编译后的输出将具有正确的扩展名,并且模块将在浏览器和 Node.js 中工作。 您不需要额外的转换工具或tsc为您做任何事情。

开发人员可以更简单地执行以下操作:如果它是 TypeScript 源文件,请编写一个 .ts 扩展名。 如果是 JavaScript 源文件,请编写 .js 扩展名。 这也将在浏览器中正确编译和运行(.ts 需要 application/javascript MIME 类型)。 扩展名应与源文件类型匹配。 tsc 和 ES 模块可以处理(至少浏览器中的 ES 模块,希望 Node.js 遵循相同)

@justinfagnani tsc对此相当满意,在这里说“不是我们的问题”是公平的,但这里似乎没有关于什么是正确的共识,

在 tsc、vscode、webpack 和 ts-node 之间,没有一个能真正与刚刚成为本机可用的标准(现在可用的浏览器和节点)完全一致。

示例 1

我想编写一个现代 javascript 包,在 typescript 中编写它并将类型剥离出来作为 ecmascript 模块分发(保留导入和导出语句)。

示例 2

我正在使用 vscode 中的模块构建一个静态网站,默认情况下,它会自动导入,每次首次导入都没有扩展名,您需要手动调整它。

在这些情况下,tsc 可以处理这个问题,甚至可以导出描述符文件,这些文件支持然后使用具有完整类型的打字稿中的 es 模块分发(真棒)。

但是如果我想对源代码运行测试代码,我可以使用 ts-node 但这里 ts-node 不高兴。

类似地,tsc、vscode 和 webpack 已经演变为执行以下操作:

  1. vscode 自动导入,不带扩展名
  2. tsc 会很高兴地将导入重写为 js 文件,这意味着 ts 文件
  3. webpack 在这里为要搜索的默认扩展做了各种魔术

四个实现(chrome、firefox、safari、node)都期望导入路径应该指向可以直接解析为文件的东西,现在所有的事情都应该受到质疑。

.js添加到导入应该是临时解决方法,而不是最终解决方案。 如果有的话,感觉就像一个黑客。 一种欺骗编译器的方法。 并且试图超越编译器是一种可怕的意识形态,可以遵循恕我直言。 我只能想象这对于 Typescript 新手来说是多么令人困惑。 一种应该帮助 Javascript 开发人员编写_更好_代码的语言。

正如许多人已经提到的那样,如果您将 .js 添加到导入中,TypeScript 将输出它们并且一切正常(至少在您的代码中)。 推理 tsc 应该添加扩展,因为它们在浏览器中不起作用,就像要求 TypeScript 修复由开发人员引起的运行时错误 - 好的,但是为什么开发人员不能自己修复它们?
不要误会我的意思,我希望 tsc 成为我唯一的开发依赖项,但有些事情不会发生并且会一直作为一个梦想。

我希望 tsc 成为我唯一的开发依赖项,但有些事情不会发生,并且会保持梦想。

我一定在 #3469 上看到了数十条跨越数年的几乎相同的评论,其中包括来自 Typescript 团队的一些评论。 “它是一个类型检查器,它应该做好一件事,不要指望它会取代你的整个工具链......”花了 3 年时间,但他们仍然添加了构建模式,因为做了一个很好的案例,它是正确的方向。

Webpack 和 rollup 都专门捆绑,我想发出 es 模块。

如果您将输出格式设置为esm并将preserveModules设置为true@alshdavid Rollup 可能对您有用: https ://rollupjs.org/guide/en/#preservemodules

也许“无扩展”导入是原罪,与浏览器的工作方式不兼容

现在是时候更正我们的源代码并包含.js扩展了:我们发现,当您今天在源代码中添加.js扩展时,一切正常:浏览器满意,打字稿不可知,并且打包者不在乎

所以也许无扩展是我们的源代码中的一个缺陷,我们的旧工具正在补偿它

在打字稿源代码的导入中编写.js扩展名完全是胡说八道。 它正在工作,它保留了输出中的扩展名,但它可能会在 VSCode 或您使用的任何编辑器中的 ESLint 规则(或 TypeScript)中出现“无法解析文件”错误而失败。 更不用说当您针对该打字稿源代码进行测试时。 那么测试工具也应该知道这种胡说八道的行为。 我几乎可以肯定它会抛出无法解析文件的错误。

基本示例

src/index.ts
src/foo.ts
test/index.ts

src/foo.ts

export default (a: number) => a + 100;

src/index.ts

// okay, editor (without ESLint) doesn't report error here
import foo from './foo.js';

export default () => {
  console.log(foo(123));
}

测试/index.ts

// errm... ts or js ext?! .ts should be the one that make sense
// and that's how the testing too will expect it to be, otherwise will throw
// but then it will detect some weird `.js` ext in the source files...
// which again won't be able to resolve... complete bullshit. 
import main from '../src/index.ts';
import foo from '../src/foo.ts';

test('some boolshit', () => {
  main();
});

test('about foo', () => {
  foo(20);
});

对不起,“废话”的用法,但这是事实。

我认为实现基本选项并不难,当编译器在导入中看到扩展名( .ts )以将其转换为.js (或者甚至可选地允许指定输出 ext 到是)。 当没有扩展时,不要保留扩展(半当前行为?)。

在 typescript 源代码的导入中编写 .js 扩展名完全是胡说八道。 它正在工作,它保留了输出中的扩展名,但它可能会在 VSCode 或您使用的任何编辑器中的 ESLint 规则(或 TypeScript)中出现“无法解析文件”错误而失败。

这不是真的。 ESLint、TypeScript、VS Code 以及我使用过的所有其他与 TypeScript 相关的工具,_just work_ 都是通过这种方法使用的。 这不是 hack,并且有一个非常合理的理由:您要导入的文件实际上是.js文件,并且您的扩展不应仅仅因为导入的文件是本地文件的一部分而更改项目,或预编译为第三方包的一部分。

同样,我以这种方式编写所有 TypeScript 代码,它解决了该线程中出现的所有问题。 输出在浏览器和 Node.js 中运行良好。 这不是胡说八道,它只是有效,_今天_。

不,这不对。 至少在最常见的情况下,输出目录是distbuildlib等。您编译的文件不在同一个目录中,它们永远不存在那里(在src/中) - 既不是在测试期间,也不是在编辑器中编码期间。

在上面的src/index.ts中拥有/看到和导入./foo.js对我来说没有任何意义 - 当然,除非你真的打算导入 js/json 文件,这完全没问题。 但是当你真的是指另一个打字稿源文件( ./foo.ts )时写./foo.js是......残酷的。 它具有误导性和混淆性,而且绝对是错误的。

我几乎可以肯定,如果我用 Jest 运行上面的test/index.ts ,它将失败并出现无法找到/解决./foo.js的错误,因为只有./foo.ts

如果我们抛开一切,为什么它允许有.js扩展名而不是.ts一个(ts 在编辑器中报告错误)? 这是基本的,简单的和愚蠢的不一致。 与 TypeScript 中的许多其他“仅仅因为”问题一样。

实际上,当实际上可以以.js扩展名结尾时,我实际上失去了为什么以这种方式命名此问题的逻辑。 我认为问题完全相反 - 它不能以.ts结尾(截至 3.7+)

为什么完全禁止进口中的.ts扩展?!

让我们澄清一下。 我说的是开发时间和经验。 我明白了为什么我们可以在编译输出中保留.js扩展名,以及为什么我们可以将.js ext 添加到.ts文件中的导入而不报告错误,我没关系。

TypeScript 是 JavaScript 的超集,但它不是 JavaScript。 JavaScript 是 TypeScript 的编译目标。

考虑到这一点,尽管知道 TS 将编译为 JS,但我希望 TypeScript 代码能够在完全独立的庄园中运行。

在尝试导入.ts文件时编写.js假定了解目标编译类型并假定编译目标始终是 JavaScript。

如果我们将 TypeScript 编译为 Web 程序集二进制文件或使用替代运行时(例如 Deno 或 ts-node)运行 TypeScript 代码会怎样。

.js结尾的导入语句在这种情况下毫无意义。

我宁愿被迫在我的导入路径中输入.ts ,或者没有指定的扩展名假设一个 TypeScript 源文件。

很遗憾,打字稿发明了它自己的扩展,而不是像流一样的语法糖。

@phaux我永远不想使用将源文件和dist文件混合在同一扩展名下的项目,即使它们位于不同的文件夹中。 这就像命名所有文件index.js只是因为它们位于不同的文件夹中......

这个问题(比 TypeScript 更大)似乎阻止了 Visual Studio Code 的 IntelliSense 在 Web 浏览器和 Node 中工作——这需要导入说明符中的完整(相对)文件路径。 如果没有这个,它创建的生成的import语句只能与转译器和捆绑器一起使用,比如 WebPack 和 TypeScript 本身。

VSCode 团队的回应是扩展应该由提供自动导入建议的代码添加。 我怀疑那是 TypeScript 语言服务器(不是这里讨论的tsc )。

真正了解事情的人能否确认(或纠正)VSCode 从 TypeScript 语言服务器获得自动导入建议?

情况有点复杂和微妙,所以虽然“.js”扩展最初违背了许多人优雅的直觉和理想,但打字稿偏离批准的浏览器标准和交付的实现可能是不明智和令人困惑的

所以也许我们认为当前的范式如下:
打字稿导入描述了在运行时导入的内容——也就是说,你的导入是在你的“dist”目录中执行的,而不是“src”——比如fetch调用和其他的——如果我们能容忍这个想法,剩下的就变成了简单一致

让 typescript 盲目地将“.js”附加到无扩展名导入上,就像 node 对 commonjs 所做的那样(值得注意的是,node esm 支持需要“.js”,就像今天的 typescript 一样) ——盲目附加“.js”会产生新的问题,比如导入真正无扩展名的 javascript 文件,甚至是 css 或 json 等其他文件类型——这会使扩展名有意义并需要特殊的解析逻辑,从而在浏览器和 typescript 语义之间产生明显差异——我们显然不会去以相同的方式重写fetch调用,所以也许import应该保持相似?

但是,我确实想知道 - typescript 将“.ts”扩展名转换为“.js”相对无害吗?

当然,这将导致无法导入具有“.ts”扩展名的javascript文件——但也许这是我们可以容忍的一种时髦魔法? 另一方面,这是开发人员必须学习和理解的浏览器和打字稿语义之间的一个时髦神奇的特殊差异(肯定会产生奇怪的gotchya) - 所以我的直觉是将事情留在他们今天的位置

另一方面,如果我们保持原样,魔法只是简单地重新定位到 typescript,它必须将“.js”导入与相应的“.ts”源文件相关联——在我看来,这似乎是一个更合理的位置容纳魔法,在打字稿内部而不是践踏浏览器语义

🥃追逐

@rconnamacher@TomasHubelbauer

这个问题(比 TypeScript 更大)似乎阻止了 Visual Studio Code 的 IntelliSense 在 Web 浏览器和 Node 中工作——这需要导入说明符中的完整(相对)文件路径。 如果没有这个,它创建的生成的导入语句只能与转译器和捆绑器一起使用,比如 WebPack 和 TypeScript 本身。

我认为这里只是有些混乱——今天很可能创建一个打字稿库或应用程序,它与 vscode 和 intellisense 配合得很好,它在浏览器和节点中工作,并且在 esm 或 commonjs 中工作——一个真正通用的解决方案今天是可能的

我一直在研究一些展示这一点的开放图书馆——renrakucynicredcryptoauthoritarian

给我发一封电子邮件,我很乐意解释更多

:wave: 追逐

另一点是它会导致您正在访问的文件混乱。 当您并排放置一个.js.ts文件时,在模块解析后tsc所看到的内容已经很模糊。

/folder
  a.ts
  a.js
  index.ts

如果在index.ts ,当使用moduleResolution: 'node'

// points to a.ts
import * as a from './a` 

// points to a.ts
import * as a from './a.js` 

// compiler emits error
import * as a from './a.ts` 

当您并排拥有一个 .js 和 .ts 文件时,tsc 在模块解析后所看到的内容已经模棱两可。

好的,当然,有点,但是如果a.js不是a.ts的转译版本,那么就出现了灾难性的错误,这不是 TypeScript 的错。

@thw0rted如果您要编译为单个 JS 包文件,TypeScript 将不会为输入的 TypeScript 文件生成 JS 对应文件,只会生成单个包文件。 在这种情况下,拥有一个 JS 文件和一个 TS 文件以任何方式都不对应是完全合法的。

这不是“非法的”,但这并不意味着这是一个好主意。 你有一个具体的例子来说明你真正想要这个吗?

在没有捆绑器、加载器的情况下使用 TypeScript 并以一种发出现代浏览器可以直接使用的 JavaScript 的方式使用它会很好。

举个例子:
https://github.com/alshdavid/tsc-website

但我确实感受到了问题的痛苦。 TypeScript 团队不想实现模块解析。 这与 TypeScript 编译器无法在编译时将paths转换为其实际相对路径的原因相同。

将导入从.ts转换为.js ,或者将.js添加到没有扩展的导入意味着 TypeScript 团队将实现模块解析,这超出了范围。

一个解决方案是让 TypeScript 团队提供一种引入扩展的方法,这样我们就可以实现这些东西。

一个解决方案是让 TypeScript 团队提供一种引入扩展的方法,这样我们就可以实现这些东西。

他们这样做了,只是没有通过tsc公开(仅通过编译器的编程调用)。 幸运的是,有人围绕核心编译器构建了一个包装器,其功能_exactly_就像tsc ,除了它支持通过配置进行扩展。 使用这个 tsc 包装器,您可以使用像https://github.com/Zoltu/typescript-transformer-append-js-extension/这样的扩展来自动添加.js扩展。

@alshdavid您在 GitHub 中的示例确实可以在没有捆绑程序的浏览器中使用,如果您只在导入中包含.js扩展名。 我提交了 PR 来修复它。

@alshdavid可能你对我的示例项目感兴趣(esm for modern & IE11 with SystemJS)。
但我不得不手动解析 esm 模块 =(

原则上,将.js放入我的所有import中没有问题,但不幸的是,它暴露了与某些工具的不良交互。 例如,ts-node 阻塞: https://github.com/TypeStrong/ts-node/issues/783。 关于这是谁的责任似乎没有明确的协议 - TypeScript 发出.js扩展名(在某些情况下)或使用 TypeScript 源进行翻译的每个工具。 只要每个人都在推卸责任,用户就会遭受文档不完整的插件或笨拙的服务人员来解决互操作不兼容性的问题。

有这方面的更新吗? 老实说,我不敢相信如果没有像 Webpack 这样的外部模块加载器或捆绑器,今天似乎无法正常使用 TypeScript。 这似乎很奇怪,也让我感到沮丧。 如果我必须使用一些外部模块来使 TypeScript 完全工作,那么应该开始。

我正在尝试在我的 Electron 应用程序中使用它,这就是为什么我看不到使用 Web 捆绑器的原因,并且如果不需要,我也不想安装外部模块加载器。 但这让我很生气。 甚至无法进行基本的 TypeScript + Electron 设置,尽管两者都被称赞为最好的解决方案。 老实说,这太疯狂了。 我不知道我是否只是错过了一些东西,但无法找到适当的解决方案不仅仅是令人沮丧。

如果真的不是我,我不明白为什么这个问题在 3 年内没有解决......

我最近完成了打字稿教程,然后当我尝试制作一些 ts 文件时,我遇到了这个问题。 我对这个 js-ts 世界完全陌生,所以如果有任何错误或误导/误导,请原谅我。

我所做的是,在导入时

我只是在ts文件中添加了.js扩展名,然后当我检查智能感知时,当我使用 tsc 对其进行转译时,它也将 .js 扩展名添加到生成的输出文件中。

以下是我所做的。
tsc -p tsconfig.json

{
“编译器选项”:{
//“模块”:“AMD”,
“模块”:“es6”,
“目标”:“es6”,
“noImplicitAny”:假,
“removeComments”:是的,
“preserveConstEnums”:假,
//"outFile": "js",
"outDir":"js",
“源地图”:假
},
“包括”: [
“测试脚本.ts”
]
}

虽然它对我有用。

我的疑问是,因为没有 svgwrapper.js 可以从中导入
为什么/它是如何工作的? (我假设它不考虑扩展)

我附上截图以供参考。

ts-js-ext-issue

@yogeshjog这正是tsc的工作方式和应该工作的方式。 您使用将要生成的文件名导入模块。 导入的模块最初是写成.ts文件还是.d.ts / .js对不应该从导入模块中观察到。

@yogeshjog这正是tsc的工作方式和应该工作的方式。 您使用将要生成的文件名导入模块。 导入的模块最初是写成.ts文件还是.d.ts / .js对不应该从导入模块中观察到。

谢谢! @justinfagnani澄清 👍

您使用将要生成的文件名导入模块。

但是 TypeScript 也允许省略扩展名,这在 ECMAScript 中是不允许的。 在 VSCode 中使用自动导入功能时,它甚至忽略了扩展,这是最烦人的事情。 你写了一些 JS 和 TS 说没关系,然后它在运行时崩溃。

但是 TypeScript 也允许省略扩展

使用节点解析时。 tsconfig 至少设置为允许其他会对此发出警告的分辨率模式,但我的理解是,团队一直在让原生模块支持在添加更多模式之前解决。

根据 ECMAScript,这是不允许的。

ECMAScript 没有说明导入说明符。 这取决于 HTML 和 Node 等宿主环境。 TypeScript 在加载模块时支持节点解析,并根据编译器 _output_ 而不是编译器 _input_ 解释解析。 这样,说明符在编译后起作用,无论您是否尝试导入已编译的模块,它们都起作用。

因为 TypeScript 使用节点解析来进行路径搜索以转换'./foo'信息'./foo.js' ,所以'./foo'是有效的。 但是 Node 解析不会把'./foo.ts'变成'./foo.js' ,所以'./foo.ts'是无效的。

但这在节点分辨率范围内。 如果您尝试使用其他环境,例如 HTML 或 Node 的本机模块支持,那么 TypeScript 和主机将不同意什么是有效的。

希望本机模块支持现在已经足够稳定,以便 TypeScript 添加另一个分辨率设置。

@leontepe您不需要捆绑器/加载器。 您可以使用https://github.com/Zoltu/typescript-transformer-append-js-extension/ (我的首选解决方案),也可以将.js附加到所有导入语句(请注意:这将使您的代码在ts-node中失败)。

@leontepe您不需要捆绑器/加载器。 您可以使用https://github.com/Zoltu/typescript-transformer-append-js-extension/ (我的首选解决方案),也可以将.js附加到所有导入语句(请注意:这将使您的代码在ts-node中失败)。

@MicahZoltu好吧,谢谢,但仍然有点令人沮丧,因为它不仅仅“开箱即用”。 正如@phaux所说,VSCode 建议其用户在没有扩展名的情况下进行导入语句,这在运行时根本不起作用。 我在这里严重质疑 TypeScript 团队的能力,或者我一直以来都出错了,简直是愚蠢的。 但谁知道...

编辑:此时认真考虑只为 JavaScript + Babel 放弃 TypeScript。

我还没有写过 Electron 应用程序,但是有没有可能让它做 Node 风格的解析,那里有一种算法可以尝试解析导入,它会以固定的顺序尝试几件事? 鉴于所有来源都是从本地资源加载的,似乎应该没问题...

也许是时候为 TS 添加新的模块分辨率了? Node.js 现在有原生 ESM 支持,但是在相对路径中没有显式扩展,它会强制使用--experimental-specifier-resolution=node标志,这有点烦人。 还有 Deno 和浏览器。 理想情况下应该有这样的东西:
tsconfig.json:

{
    "compilerOptions": {
        "moduleResolution": "explicit",
        "strict": true
    }
}

然后打字稿:

import foo from './foo.ts'; // no ts(2691) here

编译为 Javascript:

import foo from './foo.js';

“严格”:真正意味着大量的工作!
2020,还没有解决,可笑。

只是插话说这个问题也阻碍了我们的团队😢

我们真的很希望能够拥有一个构建到原生模块的单一源代码,并且在 node 和 webpack 中也可以使用。 如果您手动将 .js 放入 TS 代码的导入中,Webpack 似乎会出现问题。 但是,如果您将 .ts 文件扩展名放在导入中,您可以让 webpack 工作,但打字稿当然会抱怨。 没有文件扩展名会导致没有本机模块。 因此,似乎没有任何组合可以满足浏览器和捆绑程序。 如果我们可以在我们的导入中编写 .ts 并将其变为 .js 在输出中,我相信问题将得到解决(并且您也会获得 deno compat)。 @evg656e 之类的建议似乎应该有效。

@EisenbergEffect是否有针对 WebPack 的错误提交? 听起来确实像 WebPack 问题,与正常的 tsc 不同。

@justinfagnani我不确定问题出在 webpak、ts-loader 还是其他地方。 我确实花了很多时间试图让我的最佳构建设置到位,并且没有遇到任何选中所有框的解决方案。 可能有一些方法可以通过自定义转换器或构建后步骤来实现,但我不愿意采用非标准设置,因为它可能会对下游开发人员产生负面影响。

@EisenbergEffect如果 WebPack 的 TypeScript 加载器不允许您像tsc那样通过.js扩展名导入 TypeScript 文件,那么这是一个错误。 就像你说的那样,这种行为阻止了使用 WebPack 和tsc构建相同的源代码。

也许是时候为 TS 添加新的模块分辨率了? Node.js 现在有原生 ESM 支持,但是在相对路径中没有显式扩展,它会强制使用--experimental-specifier-resolution=node标志,这有点烦人。 还有 Deno 和浏览器。 理想情况下应该有这样的东西:
tsconfig.json:

{
    "compilerOptions": {
        "moduleResolution": "explicit",
        "strict": true
    }
}

然后打字稿:

import foo from './foo.ts'; // no ts(2691) here

编译为 Javascript:

import foo from './foo.js';

人们(例如,我)希望让一段代码同时在 Node.js、Deno 和 Web 中运行。 我认为这将是 TypeScript/JavaScript 编程的一个重要里程碑。

如果您正在使用 vscode 并需要临时解决方案,将其添加到settings.json似乎可以解决问题:

"typescript.preferences.importModuleSpecifierEnding": "js"

它会将.js附加到您的导入路径(这将毫无错误地解析 src 中的*.ts文件,但在转译时保留.js )。 在没有捆绑器的情况下使用tsc时很有用。

在我们等待 tsc 解决方案时,对此的低技术解决方案是

  1. 将所有源文件复制到临时文件夹
  2. 在构建之前删除导入和导出的扩展

我想分享这个单行,以防其他人可以使用它。 它将 src/ 文件夹复制到 tmp/ 并更改那里的文件。

npx shx cp -r ./src/ ./tmp/ && npx rexreplace "(^§s*?(?:import|export).*?from§s+?(['\"]).*?)§.ts§2" €1€2 './tmp/**/*.{ts,js,tsx,jsx}'

作为 package.json 中构建脚本的一部分(在执行yarn add --dev shx rexreplace之后),它可能看起来像这样

"scripts":{
  "build": "yarn build-esm && yarn build-tsc",
  "buil-esm": "...Whatever you normally do...",
  "build-tsc": "shx mkdir -p tmp && shx cp -r ./src/* ./tmp && rexreplace \"(^§s*?(?:import|export).*?from§s+?(['\\\"]).*?)§.ts§2\" €1€2 './tmp/**/*.{ts,js,tsx,jsx}' && tsc src/index.ts && shx rm -r ./tmp"
}

我目前将.js保留在 TypeScript 源中的导入路径之外,以让 Jest 满意。
然后,我有一个后处理步骤来添加.js
此方法不支持源映射。

tsc -b tsconfig-solution.json -w --listEmittedFiles \
  | node mk/build-post.js

build-post.js后处理脚本执行以下操作。

.js附加到importexport的相对路径

例如,

export { X } from "./first";
import { Y } from "./second";

变成

export { X } from "./first.js";
import { Y } from "./second.js";

请注意,我没有在任何地方使用index.ts ,而是按照 Deno 约定使用mod.ts 。 因此,我不需要考虑附加/index.js的情况。

将 CommonJS 包的import更改为require

我的代码在节点 ^12.17 和 ^14.1 上以模块模式运行。 我只发布 ES Modules。
但是,许多依赖项的"main"字段中仍然有 CommonJS。
因此,我应该将这些更改为 CommonJS,除了 NodeJS 内置模块。

例如,

import { Server as WsServer } from "ws";

变成

import { createRequire } from "module";
const require = createRequire(import.meta.url);
const { __importDefault } = require("tslib");

const { Server: WsServer } = require("ws");

但是,webpack 对这些require不满意,所以我必须使用:

/// #if false
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const { __importDefault } = require("tslib");
/// #endif

/// #if false
const { Server: WsServer } = require("ws");
/*
/// #else
import { Server as WsServer } from "ws";
/// #endif
/// #if false
*/
/// #endif

在 webpack 中,任何导入我的项目的包都必须使用ifdef-loader ,然后 webpack 会看到原来的import行。

我们也受到了打击,我无法理解这在被报告 3 年后怎么没有得到解决。
我们正在使用 WebStorm,因此 VSCode 特定设置将不起作用。
使用单独的脚本来修复输出是荒谬的。
这应该可以在不必使用第三方工具的情况下工作。

+1

这是我使用 asp.net core 3.1 网站的解决方法(可能适用于较低版本)
在 startup.cs 中:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            var rewriteOptions = new RewriteOptions();
            rewriteOptions.AddRewrite(@"^js/(.+)", "js/$1.js", skipRemainingRules: true);

            app.UseRewriter(rewriteOptions);

            app.UseStaticFiles();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });
        }

重写中间件会将扩展名“.js”添加到针对文件夹 /js 的任何请求。
警告:中间件的顺序在这里很重要,UseStaticFiles 必须放在 UseRewriter 之后,否则它将不起作用。

和这里的每个人一样,我更喜欢开箱即用的解决方案,但在那之前......

也许是时候为 TS 添加新的模块分辨率了? Node.js 现在有原生 ESM 支持,但是在相对路径中没有显式扩展,它会强制使用--experimental-specifier-resolution=node标志,这有点烦人。 还有 Deno 和浏览器。 理想情况下应该有这样的东西:
tsconfig.json:

{
    "compilerOptions": {
        "moduleResolution": "explicit",
        "strict": true
    }
}

然后打字稿:

import foo from './foo.ts'; // no ts(2691) here

编译为 Javascript:

import foo from './foo.js';

我在eslint-plugin-node中设置了node/file-extension-in-import规则来检查配置,其想法是手动将扩展名添加到导入语句(因为这样更好)和 TypeScript 中的"moduleResolution": "node"配置,我只能看到ts(2691)

希望@evg656e所说的可能值得实施。

我不敢相信这仍然是一个问题。

当你使用
"baseUrl": ".", "paths": { "/*": ["./*"] },

您可以像这样进行绝对模块导入:
`从“/嘿/连接”导入ws;

但是当添加“.js”扩展名时,突然间编译器不再找到 connection.ts 声明并说:

Could not find a declaration file for module '/hey/connection'. '/home/tobi/Documents/JITcom/Code/Libs/Test_Browser/hey/connection.js' implicitly has an 'any' type.

使用相对路径一切正常。

请解决这个问题

我也想解决这个问题。

谢谢你。

可笑的是,这仍然是一个问题! :00

我终于决定改用 TypeScript,这是我遇到的第一个问题。 它破坏了 Node 的本机实现中的模块解析,我只能通过使用 esm 包来解决它,它在接受没有扩展名的名称方面似乎更灵活,或者(奇怪的是)在导入路径中使用 .js 扩展名在我的 .ts 文件中,即使导入引用的是 .ts 或 .tsx 文件。 我不确定 TypeScript 编译器为什么接受它,但至少构建输出包含 .js 扩展名。 非常hacky的解决方案。

这些天我什至没有在客户端使用打字稿,并没有过多地影响我的代码质量并使事情变得更简单。 可能 JavaScript 会有一个点的可选类型,这个问题将不再重要。

为了让 Jest 和 Webpack 满意,我在导入行中省略了扩展。
编译时,我调用tsc --listEmittedFiles并将输出通过管道传输到后处理脚本。 该脚本添加了.js扩展,以使 Node(在模块模式下)满意。
https://github.com/yoursunny/NDNts/blob/fa6b2eb68a9f32a6a2e24e5475275f803236b8f8/mk/build-post.js

@kj

(奇怪的是)在我的 .ts 文件中的导入路径中使用 .js 扩展名,即使导入引用的是 .ts 或 .tsx 文件。 我不确定 TypeScript 编译器为什么接受它,但至少构建输出包含 .js 扩展名。 非常hacky的解决方案。

这并不奇怪,也绝对不是 hacky,并且可以完美地与浏览器和 Node 原生模块解析配合使用。

您要导入的资源_是_ .js 文件。 该类型可以在 .ts 文件或 .d.ts 文件中。 .d.ts 文件甚至不需要是 .js 模块的同级文件,并且 .d.ts 文件和 .js 文件之间不需要存在一对一的关系。

导入 .js 模块是 tsc 可以选择的最可靠和最稳定的选项。 否则会出现您导入类型而 tsc 不知道将它们重写为实际模块路径的正确方法的情况。

@justinfagnani啊,我认为这是有道理的。 我并不是要暗示 TypeScript 在这里所做的任何事情都是 hacky(我无法做出这样的声明),只是我觉得我所做的事情可能并不典型。 我想我想错了。 导入模块的不是 TypeScript,而是评估构建输出的任何内容(或者我是否误解了)? 在那种情况下,我可以明白为什么它会以这种方式工作。

您要导入的资源 _is_ .js 文件

您指的是这里的构建模块对吗? 不是源目录中的 .js 文件,而是 .ts 模块的构建版本?

难道不是如果您在模块路径中明确指定扩展名 .ts 或 .tsx ,那么输出将替换 .js 吗?

我认为可能对@kj 发生的事情有误解。 当您在未编译代码的情况下指定 .ts 扩展名时,它会引用 ts 文件。 但是,当您使用 tsc 进行编译时,它会将 ts 文件的内容转换为在您所在的环境(节点或浏览器)中可执行的 js 文件。

我了解@mkay581 ,但说我在 foo.ts 文件中有这一行:

import { Component } from './Component.js';

我所指的“Component.js”实际上是文件系统上的“Component.tsx”(没有 .js 文件,除非 TypeScript 将其理解为指的是文件的最终转译版本?),然而TypeScript 可以很好地接受这一点,然后输出中会出现相同的导入行(使用 .js 扩展名,然后可以与 Node 或浏览器一起使用)。

传统的 TypeScript 方式 (AFAIK) 将指定不带扩展名的导入行,如下所示:

import { Component } from './Component';

这显然也可以很好地编译,除了转译的 .js 文件中的导入行不包含 './Component' 上的 .js 扩展名,这是某些环境所必需的(可能听起来像规范,虽然我没有'请务必阅读它)。

另一方面,如果我用源文件的实际文件名指定行:

import { Component } from './Component.tsx';

然后 TypeScript 无法识别路径并建议我应该尝试不带文件扩展名的导入行(如果我记得,我现在不在我的电脑旁)。

所以,第一个例子对我来说有点奇怪,因为我正在编写 TypeScript,我希望能够使用 .ts 或 .tsx 扩展名导入,但它只接受没有扩展名的路径(这似乎是与 ES 模块系统的其他实现不一致)或 .js 扩展名只能引用输出文件,因为我没有这样的源文件。

@justinfagnani

您要导入的资源_是_ .js 文件。

我不知道 tsc 是如何工作的,但这对我来说听起来不对。 当您编写代码时,还没有 .js 文件。 创建它是编译器的工作。

TS 充当 JS 之上的抽象层,.js 文件是编译步骤的输出。 指望他们在那个特定的扩展中出现似乎打破了抽象,IMO。

换句话说,当您使用 C 或 C++ 时,您不会在代码中#include.o文件,而是包含.h或最多.c.cpp

我愿意接受 Typescript 中这种行为的合理解释,但除非我遗漏了什么,否则这似乎不是。

@kj

这并不奇怪,绝对不是hacky

很难不同意。 对我来说,这就是怪异和骇人听闻的定义。 😛 你在这里做的是引用一个假设的文件。 不保证存在的文件。 (例如,在捆绑代码、使用 AssemblyScript 或 deno 时)您假设输出格式。 但是在编写不可知的代码(例如第三方模块)时,这是一个_dangerous_的假设。

@borfast @frzi是的,这基本上就是我的感受,我很高兴看到我不是唯一一个对此感到不舒服的人。 我只是尽量不做太多对 TypeScript 如此陌生的假设!

@frzi

您在这里所做的是引用一个假设文件。 不保证存在的文件

不是假设,tsc 知道它会生成它。 在使用 tsc 时,它保证存在。

另一种看待它的方式是,如果您导入 .ts 文件,您将导入一个编译后不存在的文件。

捆绑代码时

捆绑器在 tsc 之后运行

@kj提到了 .tsx 文件,它们让我的观点更加深入人心。 .tsx 的存在只是为了向该模块的编译器 _locally_ 发出该模块包含 JSX 的信号。 模块的导入者不需要知道文件中有 JSX,编译后也无法判断。 文件可以在 .tsx、.ts 和 .js/.d.ts 对之间无缝移植。

假设您要导入一个 .ts 文件:

import {Component} from './component.ts';

然后你想为组件使用 JSX,所以你将它重命名为 component.tsx。 所有的导入都应该失败吗? 假设您从 .ts 迁移到 .js/.d.ts,导入是否会再次失败?

@borfast

TS 充当 JS 之上的抽象层,.js 文件是编译步骤的输出。 指望他们在那个特定的扩展中出现似乎打破了抽象,IMO。

抽象并不像您在这里暗示的那样完整。 您可以从 TS 项目内部或外部导入 .js 文件。 您可以使用位于项目中任何位置的 .d.ts 文件将类型添加到 .js 文件。 .js 文件是编译后的真实文件。 其他一切都只是帮助编译器。

@justinfagnani我对你所说的有点迷茫,但是为什么 TypeScript 不能假设如果你给它一个带有 .ts 或 .tsx 的导入路径,它将为该模块生成一个 .js 文件并且因此应该在输出中替换后一个扩展?

@justinfagnani

不是假设,tsc _knows_ 它会生成它。 在使用 tsc 时,它保证存在。

但是这里隐含了一种你不承认的对称性。

你提到的这个“保证”是双向的。 如果,如您所说,“tsc _知道_它将生成 [js 文件]”,那么同样有效的问题是:“那么,为什么 tsc 不执行此保证并应用它_知道_的 .js 扩展名从编译的 js 中丢失?”

解释“tsc 保证 js 文件”这一事实是双向的。

所以我同意, @justinfagnani ,从某个角度来看,你的解释是有效的。 但相反的解释是“因此,tsc 应该_根据其知识行事_并为开发人员执行此过程”。 整个辩论从一开始就困扰我的是,这种解释是如何被接受的,几乎到了被嘲笑的地步。 这是不合理的。

最后,这场辩论是关于_一个审美决定,而不是一个合乎逻辑的决定_。 我的希望是,批评者在这个问题上使用一种放大和包容的语气——正如审美_观点_(不是逻辑演绎)所适用的那样。

简而言之,请承认 OP 的问题是一个完全有效的解释——也许和你自己的一样。 不需要激烈的辩论。

谢谢

@justinfagnani

另一种看待它的方式是,如果您导入 .ts 文件,您将导入一个编译后不存在的文件。

当然不会,就像.h文件没有随 C 程序的可执行文件一起分发。 这正是重点:您告诉编译器包含文件,而不是编译文件。

好吧,我对这个对话太垃圾了,无法进一步忽略它。
import语句不是打字稿的一部分,它是执行它的 JavaScript 环境的一部分。 现在,这里的关键词是JavaScript环境,因为这就是解析路径的原因。 向我展示一个在运行时导入并执行 .ts 文件的应用程序(当然,可能有一些,但它们就是我所说的 hacky)。
TypeScript 允许添加.js扩展,因为它允许在其源文件中使用原生 JavaScript。 node.js 的浏览器都不会执行 .ts 文件,即使ts-node即时编译 .ts 文件,只是将结果保存在内存中而不是将它们写入硬盘驱动器(一点也不hacky ?)。

现在,正确的 tsc 修复将是:

  • 我们有.ts文件
  • 我们在另一个文件中引用它
  • tsc 将该.ts文件转换为.js文件
  • tsc 然后用.js文件重写对.ts文件的所有直接引用(它确切地知道它是如何编译的,因此可以正确更新它)

问题:“现代网络开发”不是真正的网络开发,而是使用 node.js 导入方式,跳过扩展名(和整个文件路径)。 tsc 现在不再知道输出将是什么,因为导入my-awesome-module/test可以是任何东西,从 node-modules/my-awesome-module 中的文件test.js node-modules/my-awesome-module开始,到index.js文件在 $ node-modules/my-awesome-moduletest文件夹中,以使用非 js 文件(如./local-mocks/my-awesome-module/mock.json )的一些本地重写结束。

这就是问题所在:tsc 怎么知道你的特定项目的时髦 webpack/rollup/super-awesome-new-and-shiny-bundler 配置是什么?

再说一遍:我完全支持 tsc 重写导入路径,但它只适用于简单的项目,并且只适用于简单的项目(现在这是少数,因为即使是简单的演示页面也使用过度设计的 webpack 配置做出反应)是不值得的如果可以使用简单的自定义编写脚本来完成,请花时间。

再说一遍:我完全支持 tsc 重写导入路径,但它只适用于简单的项目,并且只适用于简单的项目(现在这是少数,因为即使是简单的演示页面也使用过度设计的 webpack 配置做出反应)是不值得的如果可以使用简单的自定义编写脚本来完成,请花时间。

你将如何在一个没有 webpack 的简单项目中做到这一点,我的意思是 tsc?

我正在阅读每个人的评论,这似乎是对打字稿使用期望的根本分歧。

当 .ts 源文件引用 .js 文件时,问题就已经开始了。

Typescript 是为 ~run~ 编译源 .ts 文件而不是 .js 文件而构建的。 如果开发人员想在他们的项目中使用 TypeScript,他们应该将他们的_整个_项目转换为 typescript,而不是留下孤立的 .js 文件并尝试引用它们。 而如果你没有时间将js文件中的内容改成Typescript语法,只需将js文件重命名为ts文件(或者使用TS的路径映射将导入映射到.js文件)。 问题解决了。 :man_shrugging:

编辑:将“运行”更正为“编译”,这就是我的意思,但我可以看到可能会有不同的解释。

@mkay581 TypeScript 从来没有打算运行任何东西,只是为了输出 JS 文件。

@valeriob简单项目最多可能只有几个文件,不需要捆绑。 现在的浏览器内置了导入,无需绕过它。 然后就像在浏览器中监听导航事件一样简单,然后将每个事件映射到匹配的路由,每个路由都可以使用fetch导入数据,并且一旦返回就可以使用非编译的模板引擎(lit- html、HyperHTML 或旧的,如 Mustache、Handlebars、Pug 等)。 完成,不需要花哨的 webpack、框架或任何实用程序库,甚至只需要监听器、regexp、fetch、promise 和一个简单的 js 模板引擎。

谢谢@Draccoz ,你能告诉我如何让这个简单的场景工作吗? https://github.com/valeriob/Typescript_Non_SPA
这是一个简单的 .ts 文件,它引用了一个 JS 库(以 rxjs 为例),我想在 html 页面中将它作为模块使用。

@valeriob这并不像它应该的那样微不足道。 大多数第三方库即使声明它们与 ES 模块兼容,它们也不在浏览器世界中。
RxJS 是我在将其引入原生流程时最感兴趣的东西,但他们在决定自己实现它之前等待这张票得到解决(有趣......)。
在我设计的架构中,我最初使用 sed 来修复所有导入,但后来我改变主意使用一个简单的开发服务器和路径重写。 我的概念是除了 rxjs、typescript 和 lit-html(具有 4 个文件夹和 ULTRA 快速 CI 测试/构建的 node_modules)之外,我的应用程序中没有外部依赖项。 如果 TS 重写了路径,它将消除对我的服务器的需要,尽管无论如何它大约有 90 行代码。 它是开源的,如果有人想要链接,请在我的个人资料中询问或查看组织。

至于简单的页面,我指的是那些不需要任何库的页面,比如点击 url -> 获取数据 -> 显示它 -> 重复。

不需要在 Js 中使用任何库是纯粹的幻想,因为 Js 没有基础库。

简单的场景是我链接的那个,它不起作用。 此外,对于不想潜入疯狂的 webpack/编译世界的 ppl 来说,这也是最常见的情况,这会使事情复杂化而没有真正的好处,非常限制您的选择,让开发人员与工具抗争比编写应用程序更多,并减慢您的开发速度环形。

许多脑力劳动者在这里否认这个问题的存在,并且它是许多开发人员生产力的一个重要问题。

解释它_不应该是一个问题_并不能_解决问题_。 软件应该符合用户的心智模式,而不是把自己的实现模式强加给用户。 (用户体验的东西)

如果用户根据@borfast将 tsc 概念化为类 C 编译器,那么这就是需要适应的特权思维模式,尽管 tsc 的实现细节是什么。

OP 的问题有 200 多个竖起大拇指,比 repo 上的所有其他问题都多。

这个问题值得社区投票。 请考虑开始投票。

如果大多数用户希望 tsc 重写他们的导入,那么这是正确的答案。 至少那是 UX 看待事物的方式。

甚至比这更简单,如果 typescript 作为一种语言它是 JavaScript 的超集,那么 tsc 的输出(甚至不是单个输出文件切换)应该可以被 JavaScript 本身消化。

@weoreference如果您以明确的方式定义(不是编写代码,只是设计)它应该如何重写路径,考虑多种环境(node.js、浏览器)、多种捆绑器配置的可能性(调查所有当前、计划和可能的未来功能) webpack、rollup 和 parcel 以及任何其他打包工具)当然可以,那么我相信 TypeScript 团队会全力支持你。

如果您不愿意这样做或认为这太难了,请停止要求他们创建一个没人能真正描述的功能,就像“我不在乎如何,但让这头牛飞起来”......

@Draccoz ,你想让你的车换档,这样你的车速就可以超过 10 公里/小时,但我敢肯定,制造商从来没有要求你了解齿轮系的工作原理,更不用说设计齿轮系了。

至于捆绑器和诸如此类的问题,为什么这是一个障碍?

看,我只想使用 ES 模块,我不想处理所有 Babel 和 Webpack 的疯狂。 为什么这不能是一个可选的编译器选项,只是将文件扩展名更改为 .js? 如果此行为与用户设置的其他配置选项冲突,则应自动禁用它,或者在执行 tsc 时显示警告/错误并停止编译,以便用户知道他们需要更改配置。

我真的不明白为什么这是不可能的。

@Draccoz你好 :) 好吧,在我看来,你在问两件事:

  1. 如何实施

  2. 作为开发人员,我如何向 tsc 发出信号,表明我希望针对我的特定环境以某种方式重命名导入

这是我的想法。

  1. 请参阅@borfast的回复; 这是一个“高阶”的答案,但简洁:)

  2. 也许我可以向 tsc/tsconfig 传递一个标志,例如“renameImports”:true,或者其他一些被 tsc 接收到的信号。 如果需要更高的精度,那么标志可能不应该是布尔值,而应该是一个字符串,可能是这样的:

标准导入扩展:'js'

所以所有没有文件扩展名的导入默认为 .js

结论:
这个功能似乎很难实现,因为您假设 Q2(导入重命名方案)需要被 Q1(即 tsc)提前知道。 但实际上,没有。 我,人类开发者,可以很容易地向 tsc 提供这个“世界知识”,而不需要知道 webpack/browsers/etc 是如何工作的。 所有这一切都带有一个简单的标志。

@borfast Transmission 不是用户要求的,它是由制造商提供的汽车,用户只是同意这是一个新功能并开始使用它 - 抱歉,这不是一个正确的论点。 与您的用户-汽车制造商示例相比,它更像是“我希望我的汽车成为一辆全电动汽车,具有 10 万英里的续航里程和喷气式飞机的最高速度。我不在乎如何实现它,我想要它并且就是这样-期间。
@weoreference在我的问题中有点误解。 我在问:如何让 tsc 了解路径指向的内容? 是直接js文件吗? 还是里面有index.ts的文件夹? 或者它是一个包含main字段的 package.json 模块? 还是 esnext 领域? 还是其他任何非标准的? 或者是被webpack重写的路径? 还是上卷? 如果是这样,配置在哪里? 可能是另一个捆绑器吗? 一些鲜为人知的? 在上面的句子中我没有想到的其他用例是什么?

也许我可以向 tsc/tsconfig 传递一个标志,例如“renameImports”:true,或者其他一些被 tsc 接收到的信号。

@weoreference你不能用路径映射做到这一点吗? TSconfig 文件中的paths选项使我们能够控制将导入路径映射到 JS 文件。 对不起,如果我解释不正确。

@Draccoz谢谢你的回复,请让我澄清一下

在上面的句子中我没有想到的其他用例是什么?

哦,是的,tsc 很难了解所有这些环境/构建工具组合。

但是 tsc 可以有一个简单的实用程序来帮助开发人员覆盖_大多数_用例,这就是我解释的“standardImportExtension”配置值。

当然,“保证”的问题就来了。 在这个线程的前面,有人认为 tsc 应该“保证”编译的 js 实际上会运行 [在某些环境中]。

嗯……也许这是一个太难的承诺。 确实,tsc 很难保证 js 将 [在某些环境中] 运行,因为正如您刚才所描述的,在当前的 js 环境中很难定义该环境。

但是@borfast提出的简单标志实用程序解决了尝试 tsc 的新用户遇到的大部分 js 导入错误。

高级用法,正如您所提到的,包括所有这些考虑因素——

是直接js文件吗? 还是里面有 index.ts 的文件夹? 或者它是一个包含 main 字段的 package.json 模块? 还是 esnext 领域? 还是其他任何非标准的? 或者是被webpack重写的路径? 还是上卷? 如果是这样,配置在哪里? 可能是另一个捆绑器吗?

— 好吧,这种用法确实可以留给自定义用户脚本、捆绑程序和其他工具。

但仅仅因为高级用法难以解决并不意味着我们应该避免解决简单的情况。

最简单的情况是为导入添加 .js 扩展名; 我们可以解决,也应该解决。

@weoreference ,所以我因提出问题并试图提供帮助而被你否决,然后你忽略了这个问题。 似乎你只是想争论和辩论,而不是真正推进到一个富有成效的解决方案。 您对每个人的尝试都投了反对票。 在这个线程上给我的最后一条消息。 为所有的垃圾邮件道歉,大家。

@weoreference@DanielRosenwasser评论中查看此对话的开头。 他解释了为什么他们不会实施它(“目前”?无用的希望给予)。 在他的发言之后,打字稿中没有人进一步评论这个话题。 对我来说,这个讨论应该结束,微软应该正式表明他们的立场。 时期。
顺便说一句-我个人完全赞成该功能,这会简化很多。 失去了希望,因为我也理解打字稿团队的论点。

@weoreference

不是假设,tsc 知道它会生成它。 在使用 tsc 时,它保证存在。

但是这里隐含了一种你不承认的对称性。

你提到的这个“保证”是双向的。 如果,如您所说,“tsc 知道它会生成 [js 文件]”,那么同样有效的问题是:“那么,为什么 tsc 不执行此保证并应用它所知道的 .js 扩展名从编译的 js 中丢失?”

对称性实际上并不成立。 它不像“应用 .js 扩展”那么简单——tsc 必须解析和重写说明符,而解析可能取决于类型声明。 TypeScript 和 Babel 支持单模块编译模式(isolatedModules 用于 tsc),在这种情况下,如果您导入类型而不是 JavaScript,则 tsc 必须加载类型声明以确定相应的 .js 文件的位置,以便重写说明符。 为了使事情保持一致并且没有极端情况,最简单的方法是仅支持导入编译前存在的 .js 文件或当前项目的已知结果。

@justinfagnani @Draccoz谢谢; 好吧,是的,我明白了。

我最后的评论是:“难的案子不能解决,不代表我们不能解决简单的案子。”

谢谢

我一直假设 TypeScript 在这里违反规范(并产生无效的 JavaScript),但我在规范中找不到任何地方实际上要求导入路径与文件名完全匹配(带扩展名)。 这很难消化,所以我并不完全有信心,但我能找到的只是规范要求“FromClause”中的“ModuleSpecifier”是“StringLiteral”。 似乎是一个非常宽松的定义,但我想这可能是为了在 ECMAScript 存在的许多不同环境中灵活地进行模块解析。 谁能确认我是否正确阅读? 如果真的是这样,我不得不软化我对 TypeScript 处理这个问题的方式的立场。 虽然,我仍然希望 TypeScript、Node 和 web 之间有更多的互操作性。 目前的情况并不理想。

读完这篇文章后,我来到了这个问题。 这个问题给我的印象是,由于 ES 规范,Node 的系统以它的方式运行(严格匹配文件名),但我现在可以看到我误读了(他们不是指 ES 规范),也许不是 Node, TypeScript 实际上也没有做任何“错误”的事情。 然后,这个问题实际上是关于不同环境之间同样有效的模块解析方法之间的不兼容性。 尽管如此,很明显 TypeScript 的主要目标环境是 Node 和 web,虽然 Node 理论上可以改变他们的方法,但我认为浏览器不太可能,所以我仍然认为这个问题是有效的。

编辑:我认为这可能是规范的这一部分似乎指定模块分辨率是“主机定义的”?

@kj相关规范文本在 HTML 规范中: https ://html.spec.whatwg.org/multipage/webappapis.html#resolve -a-module-specifier

它说导入说明符必须是完整的 URL 或绝对或相对路径。 如果说明符不解析为 URL(通常以http://https://开头)或以/./../开头

没有进行其他路径搜索或解析。 这意味着说明符必须解析为模块的完整 URL,如果服务器需要,您必须包含扩展名。 服务器确实可以选择返回无扩展 URL 的响应。

Node 采用更严格的模块解析算法的原因是为了与 Web 更兼容,因此发布到 npm 的模块不需要额外的工具即可在浏览器中本地运行更常见。 按包名导入仍然是一个非常重要的功能,因此 Node 确实支持这一点,但旨在与Import Maps 提案(Deno 和 SystemJS 已经支持)兼容。

总而言之,tsc 对使用.js扩展名导入的支持在 Node 和 Web 浏览器中都能完美运行。 实际上,在没有额外工具的情况下,无扩展的导入会导致问题。 通常,支持对包名称进行节点解析的工具也将使用类 require() 样式解析并解析相对路径并添加它们的扩展名。 这就是es-dev-server所做的。

谢谢@justinfagnani。 因此,至少在浏览器中,这实际上取决于服务器如何实现它。 很高兴知道。 这仍然相当不直观和令人困惑,我确实认为为了社区的利益,项目之间需要更多的融合,但我不再确定那会是什么样子。

编辑:现在,这对我来说就足够了(特别是考虑到上面的解释,我觉得更舒服):

https://nodejs.org/api/esm.html#esm_customizing_esm_specifier_resolution_algorithm

我不确定 TypeScript 文档中是否引用了此选项,但如果没有,也许应该是为了避免混淆。 似乎不是,搜索“ site :typescriptlang.org specifier-resolution”或“ site:staging-typescript.org specifier-resolution”。 虽然,由于这在 Node 中还是很新的,但这并不奇怪。

@justinfagnani我同意.js扩展在大多数情况下都有效,除非有人采取意识形态立场_反对_它(例如 https://github.com/TypeStrong/ts-node/issues/783,你所熟悉的)。

我在这里制作了@quantuminformation脚本的无依赖版本。 该版本还包含一个正则表达式,它将仅替换不以.js结尾的模块。

一样的需要

通过在导入语句中使用.js来实现 ESM 兼容性的.js扩展对我不起作用。 当我导入一个 TS 文件并省略任何扩展名时,它编译得很好。 当我向导入添加.js扩展时,我从 TS 编译器中得到很多错误(不应该存在)。

似乎没有开发此功能的主要原因是由于与各种捆绑程序兼容的困难/不可能,但是 Node.js 和捆绑程序在 ECMAScript 中不支持模块的情况下开发了自己的模块方案。 现在我们有了 ESM,尽管有些人可能认为它不够用,但它是前进的方向。 随着在 ESM 和 Web 组件中开发更多代码,捆绑器的使用会减少,TS 和 ESM 之间的这个痛点会产生更多摩擦。 即使最初的支持有问题并且没有考虑所有用例,如果它可以开始得到支持,那就太好了。

在有更好的解决方案之前,这是一个用作构建任务的无依赖节点脚本

在你的 package.json 中配置:

  ..
    "scripts": {
        "build": "node build.js",
   ...

//build.js
import { execSync} from "child_process"
import * as util from "util"
import * as fs from "fs"
import * as path from "path"

//function to recurse dirs finding files
function fromDir(startPath, filter, callback) {

    //console.log('Starting from dir '+startPath+'/');

    if (!fs.existsSync(startPath)) {
        console.log("no dir ", startPath);
        return;
    }

    var files = fs.readdirSync(startPath);
    for (var i = 0; i < files.length; i++) {
        var filename = path.join(startPath, files[i]);
        var stat = fs.lstatSync(filename);
        if (stat.isDirectory()) {
            fromDir(filename, filter, callback); //recurse
        }
        else if (filter.test(filename)) callback(filename);
    };
};

//this add .js to lines like:  import .* from "\.  <-- only imports from ./ or ../ are touched
function addDotJsToLocalImports(filename) {
    var buf = fs.readFileSync(filename);
    let replaced = buf.toString().replace(/(import .* from\s+['"])(?!.*\.js['"])(\..*?)(?=['"])/g, '$1$2.js')
    if (replaced !== buf.toString()) {
        fs.writeFileSync(filename, replaced)
        console.log("fixed imports at "+filename )
    }
}

//------------------------
//---BUILD TASK START 
//------------------------

execSync("npx tsc --build -verbose", { stdio: 'inherit' })

//add .js to generated imports so tsconfig.json module:"ES2020" works with node
//see: https://github.com/microsoft/TypeScript/issues/16577
fromDir("./dist", /\.js$/, addDotJsToLocalImports)

基于https://github.com/microsoft/TypeScript/issues/16577#issuecomment -310426634

任何人都记得 3 年前打开这个问题时他们在做什么吗?

非捆绑器解决方案是编写以.js作为扩展名的 Web 分布式项目。

那 100% 有效。 更大的问题是尝试编写针对 web 和 Node.js 的模块,但这通常是 JS 的问题。

如果有什么事情要做,那就是添加--moduleResolution=web ,但这不是这里问题的范围。

我必须编写这个构建后脚本来添加 .js 扩展名。

fix-ts-imports

#!/usr/bin/env sh

# Fixes JavaScript module imports generated by TypeScript without extension.
# Converts
# import {} from './module'
# into
# import {} from './module.js'
#
# EXAMPLE
# ./fix-ts-imports

ProjectDir="$(cd "$(dirname "$0")/.." && pwd)"

fix() {(
        local pkg="$1"
        shift

        find "$pkg" -type f -iname '*.js' -not -ipath '*/node_modules/*' -print0 \
        | while read -r -d '' file; do
                sed -i '' -E 's|(import .+ from ['\''"]\.?\./.+[^.][^j][^s])(['\''"])|\1.js\2|g' "$file"
        done
)}

if test $# -eq 0; then
        set -- "$ProjectDir"
fi

for pkg; do
        fix "$pkg"
done

我在 JavaScript 中有类似的脚本:
https://github.com/yoursunny/NDNts/blob/743644226fe18d48e599181e87ad571a2708a773/mk/build-post.js

它被调用为:

tsc -b mk/tsconfig-solution.json -w --listEmittedFiles \
  | node mk/build-post.js

这种脚本的主要缺点是它们破坏了源映射,因此调试变得不那么有效。

在源代码中使用现代工具和.js扩展,一切都在节点和浏览器中运行良好

  • 打字稿
  • es模块
  • 卷起

我只能假设在这里遇到麻烦的人实际上陷入了 es-modules 和节点解析的一些破碎混合,因为开发人员坚持他们旧工作流程的熟悉地标——可能是 webpack 的罪魁祸首......

— 可能 webpack 是罪魁祸首……

所以——猫从袋子里出来了。

在源代码中使用现代工具和.js扩展,一切都在节点和浏览器中运行良好

_Most_ 一切都很好,我同意源文件在其导出中都应该有.js扩展名。 以我的经验,不能很好地使用它的一件事是 ts-node,因为他们顽固地拒绝支持.js扩展。 是的,您可以在运行 node 之前添加一个预编译步骤,并且.js导入将起作用,但是如果您想直接使用 node 和浏览器运行.ts代码或测试,你目前大多不走运。 (澄清一下,我认为这是一个 ts-node 错误,而不是 TypeScript 错误)。

谢谢@chase-moskal。

我重构了 repo 和 tsconfig 文件,现在
import {something} from './something.js'
不扔
typescript force overwrite error TS5055: Cannot write file because it would overwrite input file
了,我不再需要fix-ts-imports hack了。

在 250 多条评论中,几乎没有明确的地方。 总结一下:

背景

浏览器模块

浏览器根据 URL 解析 EcmaScript 模块,包括相对 URL。 (什么工作组

Node.js 模块

Node.js 通过一种复杂得多的算法解析模块(包括 EcmaScript 和特定于 Node.js 的 CommonJS),该算法涉及多个回退和解析 package.json 文件。 ( Node.js ) 可以自定义,例如使用需要完整路径的--experimental-specifier-resolution=explicit

打字稿模块

TypeScript 有多种可用的模块解析算法和多种选项来进一步定制这些算法。 ( TypeScript ) 目的是用户编写与生成的输出中使用的相同的模块说明符,使用 baseUrl 和 pathMappings 等选项调整 tsc 的分辨率。

在实践中,大多数用户使用 node moduleResolution,以 Node.js 环境或兼容的捆绑器为目标。 此请求侧重于针对没有捆绑程序的浏览器的用户。

ts-node 模块解析

显然, ts-node不支持带有扩展名的模块标识符。 虽然不清楚为什么,因为 Node.js 和 TypeScript 都这样做,而 ts-node 表面上是它们的合并。

事实

事实 1:您可以使用 .js 扩展名

为了更广泛的兼容性(即浏览器),您现在可以使用 .js 扩展名。 除了 ts-node 的奇怪例外(IMO 错误),现在一切都可以通过指定完整路径(即包括扩展名)来工作。

事实2:它并不像“添加扩展”那么简单

这个请求最好概括为“将模块标识符转换为文件路径”。 例如./example变成./example/index.js'lodash'变成'.node_modules/lodash/index.js'

请注意,有时甚至没有解析的文件路径,例如环境模块声明。

declare module "lodash" {
}

也许模块重写仅限于当前项目/编译中的 TS 模块。

无论如何,我们现在违反了 TS 的设计参数之一,即其他模块会影响当前模块的输出。

结论

可以使用适用于 web 的模块标识符的路径。 (例如./foo/index.js而不是./foo 。)

(虽然实际上,你可能需要一个捆绑器来定位浏览器。也就是说,如果使用 npm 包。)

@pauldraper “事实 2”有一个重要问题:

这个请求最好概括为“将模块标识符转换为文件路径”。 例如 ./example 变成 ./example/index.js 并且 'lodash' 变成 'node_modules/lodash/index.js'。

您真的不想在编译时解析包之外的说明符,因为当包安装在其他地方时,这些可能不是可用的路径。 您需要在每个唯一的包安装中运行 Node 模块解析。 否则,我们可以编写并发布import {} from './node_modules/lodash/index.js'并完成它。

@justinfagnani ,我同意,但这表明模块标识符是抽象的,并且您正在操纵 tsc 之外的模块位置。 并且该 tsc 不能/不需要关注此类操作。

这个请求最好概括为“将模块标识符转换为文件路径”。 例如 ./example 变成 ./example/index.js 并且 'lodash' 变成 '.node_modules/lodash/index.js'。
请注意,有时甚至没有解析的文件路径,例如环境模块声明。

这不是这个请求的目的。 它是关于在发出代码时添加扩展。 我们想编写与 type: "module" 兼容的模块,而不需要额外的 hacky 构建步骤。 即使没有 .js,像 lodash 这样的外部模块也可以继续工作,因为它们是 CommonJs。

例子:

// ./src/moduleA.ts
export const test = 2;
// ./src/moduleB.ts
import {test} from './moduleA'



md5-ec0300a1c6d92a03c70699d0e52c0072



```js
// ./lib/moduleB.js
import {test} from './moduleA.js'

除了上面的 typescript 不支持 package.json “exports” 和 “type”。 在我管理的项目中,没有通往真正 ESM 的道路。

此请求侧重于针对没有捆绑程序的浏览器的用户。

这是不正确的。 我很少为浏览器编写 TS/JS,我主要处理服务器端代码,我也需要这个,因为我想在节点中使用 ESM 加载,而不是依赖捆绑器和额外的代码来加载模块。

即使没有 .js,像 lodash 这样的外部模块也可以继续工作,因为它们是 CommonJs。

除非他们不是。

发射时。

它可能是./moduleA.js 。 或者它可能是./moduleA/index.js ,对吧? Node.js 模块解析允许多个路径。

除非他们不是。

当这些不起作用时,你能举个例子吗? Node.js 刚刚支持从 CommonJS 导入命名导出。
https://nodejs.org/api/esm.html#esm_import_statements

它可能是 ./moduleA.js。 或者它可能是 ./moduleA/index.js,对吗? Node.js 模块解析允许多个路径。

不在 ESM 模式下。 index.js 将不再像现在那样受支持。 ESM 加载器将读取 package.json 元数据“exports”和“type”。
https://nodejs.org/api/esm.html#esm_mandatory_file_extensions

对于基于 typescript + node.js 的项目,我们需要更好的原生 ESM 故事。 这可以通过工具(打字稿)或在 node.js 中发生。问题是对于应该做什么缺乏共识。

编辑删除的指向节点 PR 的链接,因为它具有误导性。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

wmaurer picture wmaurer  ·  3评论

Roam-Cooper picture Roam-Cooper  ·  3评论

siddjain picture siddjain  ·  3评论

Antony-Jones picture Antony-Jones  ·  3评论

remojansen picture remojansen  ·  3评论