首页 > 解决方案 > 为什么打字稿允许我导入它在运行时无法使用的依赖项?

问题描述

你可以在这里看到我的示例项目: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());

标签: javascriptnode.jstypescriptcommonjses6-modules

解决方案


回答我自己的赏金问题我有点内疚,所以我要把它标记为社区。我自己写的原因是因为我觉得其他答案真的掩盖了线索。我必须在阅读之后进行数小时的自己的研究才能写出这篇文章。既然如此,我认为我的回答将对那些从我的船上开始的人更有帮助,在那里我不知道我不知道什么。我也认为这个问题还有一个额外的解决方案,尽管它会更具侵入性。

我最初的问题是,“如果第一个版本无效,为什么打字稿不通知我?” 这是另一个答案的解释:

因为您启用了 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使用的模式,但我已经花了足够的时间研究这个,所以我将把它留到另一天/问题。


推荐阅读