【问题标题】:Migrating node code base to TypeScript: global scope?将节点代码库迁移到 TypeScript:全局范围?
【发布时间】:2016-03-15 06:48:33
【问题描述】:

我正在尝试将大型节点代码库迁移到 TypeScript。为方便起见,我只想从将 .js 文件重命名为 .ts 文件开始,修复任何语义问题,无需太多重构,然后逐步完善代码库。

让我们考虑以下示例:

logger.js:

exports = function(string) {
    console.log(string);
}

moduleA.js:

var logger = require('./logger')    
exports.someAction = function() {
    logger.log('i'm in module a')
}

moduleB.js:

//var logger = require('./logger')    
exports.someOtherAction = function() {
    logger.log('i'm in module B')
}

moduleC.js:

var moduleA = require('./moduleA');  
var moduleB = require('./moduleB');  
exports.run = function(string) {
    moduleA.someAction();
    moduleB.someOtherAction(); //exception here - logger not defined
}

所以如果我执行 moduleB.someOtherAction() 我会得到一个异常,因为在 moduleB.js 的范围内没有定义记录器。

但是 typescript 编译得很好,因为 logger 是在 moduleA 中声明的,这是因为(如果我理解正确的话)typescript 将所有这些文件视为一个编译单元。

那么有没有办法避免这种情况而无需进行大量重构?

更新

我创建了一个示例项目which can be found here 如果我运行 typescript 编译器,我不会收到任何错误,尽管 logger 在 moduleB.ts 中被注释掉了:

g@w (master) ~/projects/ts-demo: gulp generate
[10:39:46] Using gulpfile ~/projects/ts-demo/gulpfile.js
[10:39:46] Starting 'generate'...
[10:39:46] Starting 'clean'...
[10:39:46] Finished 'clean' after 11 ms
[10:39:46] Starting '<anonymous>'...
[10:39:47] Finished '<anonymous>' after 1.4 s
[10:39:47] Finished 'generate' after 1.41 s
g@w (master) ~/projects/ts-demo: 

更新 2

好的,这是TypeScript deep dive book 中所述的预期行为:

如果您现在在同一个项目中创建一个新文件 bar.ts,TypeScript 类型系统将允许您使用 foo.ts 中的变量 foo,就好像它是全局可用的一样

【问题讨论】:

  • 这是不正确的。 Typescript 将每个文件视为一个单独的单元。 (声明文件除外)。 modelB 中logger 的值/类型是什么?将鼠标悬停在 IDE 上应该可以看到它。
  • 它说 var logger: any。我创建了一个演示项目,请参阅更新的问题
  • “update 2”语句是正确的,但在定位节点时不是。它将以与节点相同的方式工作,即;每个文件都是它自己的“模块”,只有在正确导入的情况下才能在另一个文件中使用。
  • 我更新了我的答案,你已经成功地为自己创造了一个有趣的案例。但它的根源在于,在为 commonjs 编译时,每个文件都是自己的封闭范围,因此您总是需要将一个文件导入另一个需要访问它的文件。
  • 您缺少“模块”配置变量,因此 Typescript 将您的整个项目视为一个文件。检查我的答案。

标签: javascript node.js typescript


【解决方案1】:

打字稿中有一种叫做内部/外部模块的东西。

内部模块可以声明可以在项目的任何地方引用的东西。考虑一下类型/lib 文件,您应该能够在项目中的任何位置引用 ErrorString 之类的内容。

另一方面,必须导入外部模块才能被引用。这就是你想要做的。

内部模块和外部模块的区别在于外部模块使用importexport。 内部使用declare module/namespace/var

只需将"compilerOptions": {"module": "commonjs"} 添加到您的tsconfig.json 文件即可启用外部模块。

【讨论】:

  • 这没什么区别。仍然编译得很好:)
  • 好吧,这些文件仍然被视为内部模块,因为它们没有导入/导出语句。 “exports.someAction”不起作用。您需要使用“导出函数 someAction()”。将任何导入/导出语句添加到该文件中,问题就会出现。
  • 当您在内部模块中说“var something”时,它会被识别为全局变量。一开始我发现这真的很令人困惑——理解内部模块和外部模块之间的区别——但最终事情就是这样。
  • 顺便说一句,永远不要使用“var”。用“let”替换所有内容并将一些更改为“const”。
  • 一切都很好,但是我们现在开始讨论 typescript 是如何工作的,这里忽略了要点——我有数百个这样的文件,我没有迁移的奢侈它们全部为 typescript 导出语法,或者突然将所有内容更改为 let 或 const。幸运的是,一切都被测试覆盖了,所以我必须忍受它。
【解决方案2】:

路易是对的。每个文件(使用 CommonJS 为 node 编译时)都像自己的模块一样创建。就像节点中的正常情况一样。

所以要让它工作,你可以这样做:

logger.ts

export default (str: string) => console.log(str);

moduleA.ts

import logger from './logger';

export var someAction = () => {
     logger("i'm in module a");
}

moduleB.ts

import logger from './logger';

export var someOtherAction = () => {
    logger("i'm in module B");
}

moduleC.ts

import { someAction } from './moduleA';  
import { someOtherAction } from './moduleB';

someAction();
someOtherAction();

我也用了一个tsconfig.json

{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true
    },
    "files": [
        "logger.ts",
        "moduleA.ts",
        "moduleB.ts",
        "moduleC.ts"
    ]
}

编译此文件(只需在包含 tsconfig.json 的文件夹中键入 tsc)会为每个 .ts 文件生成一个 .js 文件。它应该编译并运行得很好。您需要从中获得的主要内容是,在为 node/CommonJS 编译时,每个文件都是其自己的自包含模块。

编辑由于有问题的编辑:

好的,再次查看您的代码后,有几个原因可以编译但无法正常运行:

  1. 如果您将一组文件发送到编译器,则所有这些文件中的所有引用,并且全局范围内的所有变量都将在发送到编译器的所有 .ts 文件中可用。这令人困惑,有时令人遗憾,但目前事情就是这样。在您的情况下,这导致在 node.d.ts 的全局范围中定义的 require 函数在所有 .ts 文件中都可用,而不仅仅是在 moduleC.ts 中;如果从 moduleC.ts 中删除引用,您将在 logger.ts 中收到错误,因为例如未定义 require 函数。它还导致在 logger.ts 中定义的 var logger = .. 将被视为在其他 .ts 文件中可用。 Typescript 假定它将在运行时可用.. 在为 node 编译时并不理想,但当您编写实际的 typescript 而不是试图编译纯 javascript 时这不是问题。事实上,由于它是纯 javascript,编译器不会将其识别为节点模块,而是将其视为通用 javascript 文件。

  2. 使用 commonjs 编译时,每个文件都由 typescript 编译器自行编译。如果发现任何 referencesimports 将被跟踪并用于输入和验证代码当然,但编译仍以每个文件为基础完成 (@987654328 @ 不是导入,它被编译器解释为一个变量,由一个以字符串作为参数的函数分配。)

这就是它编译的原因,让我们继续讨论为什么它不工作。

commonjs 模式下编译器输出的每个文件都是它自己的“node-module”。如果您想从节点中的另一个模块访问一个模块(这实际上与打字稿无关,这只是节点的工作方式),您将需要该文件。在 moduleB.ts 中(以及在 moduleB.js 中)没有指示节点说明记录器是什么。所以你只需要需要记录器。

在您在第二次更新未使用 commonjs 的问题中所指的示例中,而且它与打字稿内部模块有关 - 而不是节点模块,这是完全不同的事情。

所以简单的答案是,如果你想在另一个节点中使用一个文件,你必须要求它。简单地取消注释 moduleB.ts 中的注释引用应该可以启动并运行所有内容。

在您的代码的当前状态下,打字稿编译器对您没有多大帮助,因为它真的帮不上忙。该代码接近纯 javascript,要从编译器获得体面的帮助,您需要开始将其转换为 typescript。第一步可能是将var asdf = require("") 替换为import asdf = require("");,这将使编译器能够检查引用并为您提供很多帮助。

我知道这是一个冗长的答案,但是您设法为自己创造了一种令人困惑且难以解释的行为。

【讨论】:

  • 是的,这涉及到模块的完全重写,正如我所说,我想避免这种情况,因为我有数百个,而且迁移会花费太多时间。
猜你喜欢
  • 2016-02-26
  • 1970-01-01
  • 2012-01-20
  • 1970-01-01
  • 1970-01-01
  • 2020-12-07
  • 2019-01-23
相关资源
最近更新 更多