use*_*840 16 javascript node.js npm node-modules typescript
这似乎是一个微不足道的问题,但需要使用什么设置/配置来解决这个问题并不是很明显。
下面是Hello World程序目录结构和源代码:
目录结构:
| -- HelloWorldProgram
| -- HelloWorld.ts
| -- index.ts
| -- package.json
| -- tsconfig.json
Run Code Online (Sandbox Code Playgroud)
索引.ts:
import {HelloWorld} from "./HelloWorld";
let world = new HelloWorld();
Run Code Online (Sandbox Code Playgroud)
HelloWorld.ts:
export class HelloWorld {
constructor(){
console.log("Hello World!");
}
}
Run Code Online (Sandbox Code Playgroud)
包.json:
{
"type": "module",
"scripts": {
"start": "tsc && node index.js"
}
}
Run Code Online (Sandbox Code Playgroud)
现在,执行该命令会 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'
}
Run Code Online (Sandbox Code Playgroud)
很明显,这个问题似乎源于这样一个事实,即在index.tsTypescript 文件.js中,import 语句 ( 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.
enr*_*r99 37
如果您使用 VS code,则可以使用正则表达式来替换路径。
寻找:(\bfrom\s+["']\..*)(["'])
代替:$1.js$2
该解决方案的灵感来自于之前的解决方案,但由于下述原因,该解决方案效果更好。请注意,此解决方案并不完美,因为它使用正则表达式而不是对文件导入进行语法分析。
忽略 npm 导入。例子:
import fs from 'fs'
Run Code Online (Sandbox Code Playgroud)
支持多行导入。例子:
import {
foo,
bar
} from './file'
Run Code Online (Sandbox Code Playgroud)
支持as进口。例子:
import * as foo from './file'
Run Code Online (Sandbox Code Playgroud)
支持单引号和双引号。例子:
import foo from './file'
import foo from "./file"
Run Code Online (Sandbox Code Playgroud)
支持出口。请参阅导出文档。例子:
export { foo } from './file'
Run Code Online (Sandbox Code Playgroud)
小智 27
您还可以添加 nodejs CLI 标志以启用node module resolution:
--experimental-json-modules--experimental-specifier-resolution=nodenode --experimental-specifier-resolution=node dist/some-file.js
Run Code Online (Sandbox Code Playgroud)
值得一提的是,--experimental-specifier-resolution=node如果有一个错误(或没有),那么您就无法运行bin没有扩展名的脚本(例如在 package.jsonbin部分中,"tsc"不会工作,但"tsc":"tsc.js"会工作)。
太多包的bin脚本没有任何扩展名,因此添加NODE_OPTIONS="--experimental-specifier-resolution=node"环境变量会遇到一些问题
use*_*840 18
对于正在寻找此问题解决方案的开发人员,我们遇到的可能解决方法如下:
对于新文件,可以".js"在编辑时简单地在 Typescript 文件的 import 语句中添加扩展名。例子:import {HelloWorld} from "./HelloWorld.js";
如果使用旧项目,而不是遍历每个文件并更新导入语句,我们发现".js"通过简单的自动化脚本简单地批量重命名并从生成的 Javascript 中删除扩展名更容易。但是请注意,这可能需要对服务器端代码稍作更改,以将这些".js"具有正确 MIME 类型的无扩展名文件提供给客户端。如果您想避免这种情况,您可能需要使用正则表达式来批量查找和递归替换导入语句以添加.js扩展。
边注:
对于 TS 团队在解决这个问题上的失败,似乎有一种倾向,试图将这个问题断章取义,而不是将其附加到一些设计原则上进行辩护。
然而,实际上这只不过是编译器如何不对称地处理扩展名的问题。Typescript 编译器允许没有扩展名的 import 语句。然后在文件被翻译时继续将“.js”扩展名添加到相应的输出文件名,但对于引用此文件的相应导入语句,它忽略了它在翻译期间添加了“.js”扩展名的事实。脱离上下文的 URI 重写原则如何保护这种不对称性?
Typescript 文件和编译时生成的 Javascript 输出文件有固定的一一对应关系。如果引用的导入不存在,编译器会抛出错误。这些文件甚至不会编译!因此,在上下文或不可编译的示例中提到其他冲突 URI 的可能性会使此类声明无效。
如果编译器只是生成无扩展名的输出文件,它也可以解决这个问题。但是,这是否也会以某种方式违反有关 URI 重写的设计原则?当然,在这种情况下,可能存在其他设计原则来捍卫这一立场!但这样的顽固岂不是进一步验证了TS团队在这个问题上的顽固或无知?
Pat*_*ick 11
另一种解决方案是使用tsc-alias它有一个resolveFullPaths选项。
尝试用完全解析的路径替换不完整的导入路径(不以 .js 结尾的路径)(以实现 ECMAScript 模块兼容性)
我最初安装和设置是tsc-alias为了解决一些别名导入(我喜欢避免使用父导入,但在必要的情况下,例如常量或实用函数,我使用别名)。一旦工作,我发现该resolveFullPaths选项解决了OP描述的问题。
安装tsc-alias
yarn add -D -E tsc-alias
Run Code Online (Sandbox Code Playgroud)
配置tsconfig.js
{
"compilerOptions": {
...
},
"tsc-alias": {
"resolveFullPaths": true,
"verbose": false
}
}
Run Code Online (Sandbox Code Playgroud)
更新您的构建/编译脚本以tsc-alias在之后调用tsc
"scripts": {
"compile": "tsc && tsc-alias"
}
Run Code Online (Sandbox Code Playgroud)
npm i fix-esm-import-path
Run Code Online (Sandbox Code Playgroud)
只有 8 颗星(一颗星来自我),但我在多个项目中使用它,它满足了我的需要:
npx fix-esm-import-path dist/your-compiled-entrypoint.js
Run Code Online (Sandbox Code Playgroud)
小智 7
正如许多人指出的那样。TypeScript 不会也永远不会向 import 语句添加文件扩展名的原因是它们的前提是转译纯 JavaScript 代码应该输出相同的 JavaScript 代码。
我认为有一个标志让 TypeScript在导入语句中强制执行文件扩展名将是他们能做的最好的事情。然后,像ESLint这样的 linter可能会根据该规则提供自动修复程序。
如果您在使用 TypeScript 和 ESM 时遇到问题,这个小库实际上可以完美运行:
npm install @digitak/tsc-esm --save-dev
Run Code Online (Sandbox Code Playgroud)
将脚本中的tsc调用替换为:tsc-esm
{
"scripts": {
"build": "tsc-esm"
}
}
Run Code Online (Sandbox Code Playgroud)
最后你可以运行:
npm run build
Run Code Online (Sandbox Code Playgroud)