javascript - 为什么打字稿允许我导入它在运行时无法使用的依赖项?
问题描述
你可以在这里看到我的示例项目:https ://github.com/DanKaplanSES/typescript-stub-examples/tree/JavaScript-import-invalid
我创建了这个名为 main.ts 的文件:
import uuid from "uuid";
console.log(uuid.v4());
虽然打字稿可以很好地导入,但是当我尝试时node main.js
,它会给出这个错误:
console.log(uuid_1["default"].v4());
^
TypeError: Cannot read property 'v4' of undefined
at Object.<anonymous> (C:\root\lib\main.js:5:31)
←[90m at Module._compile (internal/modules/cjs/loader.js:1063:30)←[39m
←[90m at Object.Module._extensions..js (internal/modules/cjs/loader.js:1092:10)←[39m
←[90m at Module.load (internal/modules/cjs/loader.js:928:32)←[39m
←[90m at Function.Module._load (internal/modules/cjs/loader.js:769:14)←[39m
←[90m at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:72:12)←[39m
←[90m at internal/main/run_main_module.js:17:47←[39m
如果我将文件更改为此,它执行得很好:
import * as uuid from "uuid";
console.log(uuid.v4());
如果第一个版本无效,为什么打字稿不通知我?
我有一个多文件 tsconfig 设置。检查 github 项目以获取更多详细信息,但这里是可能相关的共享编译器选项:
{
"compilerOptions": {
"rootDir": ".",
"esModuleInterop": true,
"module": "CommonJS",
"moduleResolution": "node",
"composite": true,
"importHelpers": true,
},
}
这是 main.js 的外观:
不工作
"use strict";
exports.__esModule = true;
var tslib_1 = require("tslib");
var uuid_1 = tslib_1.__importDefault(require("uuid"));
console.log(uuid_1["default"].v4());
作品
"use strict";
exports.__esModule = true;
var tslib_1 = require("tslib");
var uuid = tslib_1.__importStar(require("uuid"));
console.log(uuid.v4());
解决方案
回答我自己的赏金问题我有点内疚,所以我要把它标记为社区。我自己写的原因是因为我觉得其他答案真的掩盖了线索。我必须在阅读之后进行数小时的自己的研究才能写出这篇文章。既然如此,我认为我的回答将对那些从我的船上开始的人更有帮助,在那里我不知道我不知道什么。我也认为这个问题还有一个额外的解决方案,尽管它会更具侵入性。
我最初的问题是,“如果第一个版本无效,为什么打字稿不通知我?” 这是另一个答案的解释:
因为您启用了 esModuleInterop,它也启用了 allowSyntheticDefaultImports。CommonJS 包实际上与该选项不兼容,但 TypeScript 不知道。
这是绝对正确的,但要了解正在发生的事情,这只是冰山一角:
如果您查看参考文档,建议您设置esModuleInterop
为 true。如果它降低了类型安全性,它为什么会提出这个建议?好吧,这不是它建议您将其设置为 true 的原因。事实上,这个设置并没有降低类型安全——它通过修复一些遗留的打字稿错误来增加它,特别是两个处理打字稿如何处理的错误requires
。您可以阅读文档以获取更多详细信息,但在我看来,如果您使用的是节点库,我认为将 esModuleInterop 设置为 true 是个好主意。
但!esModuleInterop 有一个副作用。在其文档的最底部,它说:
启用 esModuleInterop 也将启用allowSyntheticDefaultImports。
呃……有点。IMO,此文档不正确。它真正应该说的是,“启用 esModuleInterop 将默认 allowSyntheticDefaultImports为 true。” 如果您查看 allowSyntheticDefaultImports 文档,它会在右侧说明:
嘿,注意右上角怎么没有说推荐这个设置?这可能是因为此设置降低了类型安全性:它允许您键入import React from "react";
而不是import * as React from "react";
在模块未明确指定默认导出时进行。
通常(即allowSyntheticDefaultImports 设置为false),这将是一个错误......因为它是:您不应该能够默认导入一个模块,除非它具有默认导出。将此设置为 true 会使编译器说:“不,这很好。”
但是,当您将 allowSyntheticDefaultImports 设置为 true 时,“此标志不会影响 TypeScript 发出的 JavaScript。” 这意味着,这个标志让您可以假装库在编译时以一种方式编写,即使它不是。在运行时,这将出错。为什么设置甚至存在?我不知道,但这可能与历史原因有关:
此选项将 TypeScript 的行为与 Babel 内联,其中会发出额外的代码以使使用模块的默认导出更符合人体工程学。
似乎有一个时间点(/是?)每个人都被认为是在使用 Babel。我没有这样做,因此“符合人体工程学”的好处变成了运行时错误。
作为一种更简洁的方法,您应该使用 import { v4 } from 'uuid' 导入 uuid;
是的,但我认为将 allowSyntheticDefaultImports 显式设置为 false 也是一个好主意。它为您提供了更多的类型安全性。不仅如此,它import uuid from "uuid";
还会产生编译时错误(应该是这样)。
还有一件事我不明白:
将 allowSyntheticDefaultImports 设置为 false 也会导致导入import os from "os";
和import _ from "lodash";
编译时错误。但是当 allowSyntheticDefaultImports 为真时,这些总是运行良好。一定有一些我错过了解释为什么这些工作但uuid
没有。
我在我的 node_modules 中找不到源代码os
,但我可以查看lodash
,它index.js
是这样做的:
module.exports = require('./lodash');
在那个必需的文件中,它在底部说:
...
/*--------------------------------------------------------------------------*/
// Export lodash.
var _ = runInContext();
// Some AMD build optimizers, like r.js, check for condition patterns like:
if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
// Expose Lodash on the global object to prevent errors when Lodash is
// loaded by a script tag in the presence of an AMD loader.
// See http://requirejs.org/docs/errors.html#mismatch for more details.
// Use `_.noConflict` to remove Lodash from the global object.
root._ = _;
// Define as an anonymous module so, through path mapping, it can be
// referenced as the "underscore" module.
define(function() {
return _;
});
}
// Check for `exports` after `define` in case a build optimizer adds it.
else if (freeModule) {
// Export for Node.js.
(freeModule.exports = _)._ = _;
// Export for CommonJS support.
freeExports._ = _;
}
else {
// Export to the global object.
root._ = _;
}
我真的不明白这一切在做什么,但我认为这是定义一个_
在运行时命名的全局变量?我想这意味着,从打字稿的角度来看,这很巧合。类型声明文件没有默认值,这通常会导致运行时错误,但几乎巧合的是,由于 lodash javascript 定义了一个全局_
? 耸耸肩也许这也是一种os
使用的模式,但我已经花了足够的时间研究这个,所以我将把它留到另一天/问题。
推荐阅读
- python - 每次创建新项目时都必须在 PyCharm 中安装 numpy 吗?(安装而不是导入)
- flutter - 在颤振中使用相同的 shared_preferences 实例是安全的
- python - 如何获取 python-chess 模块中所有合法移动的列表?
- php - 验证表单 Ajax PHP
- azure - 更改 Microsoft Azure 门户登录 ID
- javascript - PHP / Axios - 正确格式化电子邮件对象以传递给 PHP 邮件程序
- javascript - 在 ckeditor5 中提交时图像 src 丢失
- docker - 如何访问 docker 容器 localhost
- encryption - 哪种密码哈希算法与平台和语言无关?
- python - 为什么我收到请求的 405 错误?