在 Typescript 编译(ES6 module)期间在相对导入语句上附加 .js 扩展名

IT技术 javascript node.js typescript npm node-modules
2021-03-04 17:57:50

这似乎是一个微不足道的问题,但需要使用什么设置/配置来解决这个问题并不是很明显。

下面是Hello World程序目录结构和源代码:

目录结构:

| -- HelloWorldProgram
     | -- HelloWorld.ts
     | -- index.ts
     | -- package.json
     | -- tsconfig.json

索引.ts:

import {HelloWorld} from "./HelloWorld";

let world = new HelloWorld();

HelloWorld.ts:

export class HelloWorld {
    constructor(){
        console.log("Hello World!");
    }
}

包.json:

{
  "type": "module",
  "scripts": {
    "start": "tsc && node index.js"
  }
}

现在,执行该命令会 tsc && node index.js导致以下错误:

internal/modules/run_main.js:54
    internalBinding('errors').triggerUncaughtException(
                              ^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module 'HelloWorld' imported from HelloWorld\index.js
Did you mean to import ../HelloWorld.js?
    at finalizeResolution (internal/modules/esm/resolve.js:284:11)
    at moduleResolve (internal/modules/esm/resolve.js:662:10)
    at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:752:11)
    at Loader.resolve (internal/modules/esm/loader.js:97:40)
    at Loader.getModuleJob (internal/modules/esm/loader.js:242:28)
    at ModuleWrap.<anonymous> (internal/modules/esm/module_job.js:50:40)
    at link (internal/modules/esm/module_job.js:49:36) {
  code: 'ERR_MODULE_NOT_FOUND'
}

很明显,问题似乎源于在index.tsTypescript 文件.js中导入语句 ( import {HelloWorld} from "./HelloWorld";) 中没有扩展名这一事实Typescript 在编译期间没有抛出任何错误。但是,在运行时 Node (v14.4.0) 需要.js扩展。

希望上下文清楚。

现在,如何更改编译器输出设置(tsconfig.json 或任何标志),以便本地相对路径导入(例如在 Typescript 到文件中的Javascript 编译期间import {HelloWorld} from ./Helloworld;被替换)import {HelloWorld} from ./Helloworld.js;index.js

笔记: It is possible to directly use the .js extension while importing inside typescript file. However, it doesn't help much while working with hundreds of old typescript modules, because then we have to go back and manually add .js extension. Rather than that for us better solution is to batch rename and remove all the .js extension from all the generated .js filenames at last.

4个回答

对于正在寻找此问题解决方案的开发人员,我们遇到的可能解决方法如下:

  1. 对于新文件,可以".js"在编辑时简单地在 Typescript 文件的 import 语句中添加扩展名。例子:import {HelloWorld} from "./HelloWorld.js";

  2. 如果使用旧项目,而不是遍历每个文件并更新导入语句,我们发现".js"通过简单的自动化脚本简单地批量重命名并从生成的 Javascript 中删除扩展名更容易但是请注意,这可能需要对服务器端代码稍作更改,以将这些".js"具有正确 MIME 类型的无扩展名文件提供给客户端。如果您想避免这种情况,您可能需要使用正则表达式来批量查找和递归替换导入语句以添加.js扩展。

边注:

对于 TS 团队在解决这个问题上的失败,似乎有一种倾向,试图将这个问题断章取义,而不是将其附加到一些设计原则上进行辩护。

然而,实际上这只不过是编译器如何不对称地处理扩展名的问题Typescript 编译器允许没有扩展名的 import 语句。然后在文件被翻译时继续将“.js”扩展名添加到相应的输出文件名,但对于引用此文件的相应导入语句,它忽略了它在翻译期间添加了“.js”扩展名的事实。脱离上下文的 URI 重写原则如何保护这种不对称性?

Typescript 文件和编译时生成的 Javascript 输出文件有固定的一一对应关系。如果引用的导入不存在,编译器会抛出错误。这些文件甚至不会编译!因此,在上下文中或不可编译的示例中提到其他冲突 URI 的可能性会使此类声明无效。

如果编译器只是生成无扩展名的输出文件,它也可以解决这个问题但是,这是否也会以某种方式违反有关 URI 重写的设计原则?当然,在这种情况下,可能存在其他设计原则来捍卫这一立场!但这样的顽固岂不是进一步验证了TS团队在这个问题上的顽固或无知?

TypeScript不可能知道你将使用什么 URI 来提供你的文件,因此它必须相信你给它的module路径是正确的。在这种情况下,您为其指定了一个不存在的 URI 的路径,但 TypeScript 无法知道这一点,因此它无能为力。

如果您使用以 结尾的 URI 为module提供服务.js,则您的module路径需要以.js. 如果您的module路径不.js.js.

请注意,W3C强烈建议不要在 URI使用文件扩展名,因为它会使您的系统更难发展,并主张改为依赖内容协商。

重写路径会破坏 TypeScript 的一些基本设计原则。一个设计原则是 TypeScript 是 ECMAScript 的适当超集,每个有效的 ECMAScript 程序和module都是语义上等效的 TypeScript 程序和module。重写路径会破坏这个原则,因为一段 ECMAScript 的行为会根据它是作为 ECMAScript 还是 TypeScript 执行而有所不同。想象一下,您有以下代码:

。/你好

export default "ECMAScript";

./hello.js

export default "TypeScript";

。/主要的

import Hello from "./hello";

console.log(Hello);

如果 TypeScript 执行了您的建议,这将打印两种不同的内容,具体取决于您是将它作为 ECMAScript 还是作为 TypeScript 执行,但是 TypeScript 设计原则说 TypeScript永远不会改变 ECMAScript 的含义当我将一段 ECMAScript 作为 TypeScript 执行时,它的行为应该与我作为 ECMAScript 执行时的行为完全相同

@user3330840:编译器无法选择它支持的语言规范的哪些部分。这是由该规范允许任何被用户写入。事实上,在我目前的项目,我关键取决于typescript离开单独的module路径的事实,因为我允许最终用户以多种不同的语言(typescript,ECMAScript中,CoffeeScript的,和JSON)提供的API兼容库,和module加载器选择正确的文件扩展名。
2021-04-17 17:57:50
...加载了一个名为foo. 如果您向 TypeScript 询问名为 的modulebar,它会将其编译为加载名为bar. 如果您向 TypeScript 询问名为 的modulebar.js,它会将其编译为加载名为bar.js. 为什么它会做其他事情?
2021-04-21 17:57:50
遗憾的是,这被否决了,因为它是正确的答案。在某些常见情况下,TypeScript 的行为方式肯定令人讨厌,但不幸的是,正确的答案并不总是与我们希望的正确答案相同。
2021-04-24 17:57:50
@DanielCassidy:正如我在上面的评论中提到的,那个“没人会写的牵强的例子”是我的一个项目中的实际工作代码。该项目取决于TypeScript准确加载我告诉它的module的事实,并且不会乱用路径。希望所有来到这个问题的观众都注意到,被接受的答案,实际上是伪装成答案的咆哮,恰好是提问者所写的。就我个人而言,我真的不明白什么是很难理解的:如果你向 TypeScript 询问一个名为 的modulefoo,它会编译它来编码......
2021-05-07 17:57:50
这个例子太牵强了,用来辩护的设计原则完全脱离了上下文,与实际问题无关,即 js 和 ts 文件之间必须存在一对一的对应关系。
2021-05-11 17:57:50

正如许多人指出的那样。Typescript 不会也永远不会向 import 语句添加文件扩展名的原因是它们的前提是转译纯 javascript 代码应该输出相同的 javascript 代码。

我认为有一个标志让 typescript在 import 语句中强制执行文件扩展名将是他们能做的最好的事情。然后像eslint这样的linter可能会提供基于该规则的自动修复程序

我通常只在typescript文件的导入语句中使用 .js 扩展名,它也能工作。

在导入路径中不使用文件扩展名是 nodejs 唯一的事情。由于您使用的不是 commonjs 而是module,因此您没有使用 nodejs。因此,您必须在导入路径中使用 .is 扩展名。