【问题标题】:Migrate Node.js project to TypeScript from plain ES6将 Node.js 项目从普通 ES6 迁移到 TypeScript
【发布时间】:2019-06-05 20:05:33
【问题描述】:

开始将 Node.js 项目从普通 ES6 迁移到 TypeScript。

我做了什么:

npm install -g typescript
npm install @types/node --save-dev

设置tsconfig.json:

{
     "compilerOptions": {
         "emitDecoratorMetadata": true,
         "experimentalDecorators": true,
         "moduleResolution": "node",
         "module": "commonjs",
         "target": "es6",
         "sourceMap": true,
         "outDir": "dist",
         "allowJs": true,
         "forceConsistentCasingInFileNames": true
     },
     "exclude": [
         "node_modules",
         "dist",
         "docs"
     ]
}

将所有文件扩展名从.js 更改为.tsnode_modules 除外):

find . -not \( -path node_modules -prune \) -iname "*.js" -exec bash -c 'mv "$1" "${1%.js}".ts' - '{}' \;

现在运行 tsc 会导致大量错误,例如:

server.ts:13:7 - error TS2451: Cannot redeclare block-scoped variable 'async'.

13 const async = require('async');
     ~~~~~

或者这些:

bootstrap/index.ts:8:7
8 const async = require('async');
        ~~~~~
'async' was also declared here.

更新:

retry 和其他 npm 包也会发生同样的情况:

const retry = require('retry');

require 语句更改为 ES6 import 语句主要解决了这些问题,但一次迁移数千个文件是不可行的,所以我需要一种方法来坚持使用 require 一段时间。这可能吗?

【问题讨论】:

  • 我猜是因为 async/await 都是 typescript 中的两个关键字,所以您不能将变量命名为 async。顺便说一句,在相关说明中,您可能希望完全停用异步库,并将 async/await 与基于 Promise 的构造一起使用。
  • 是的,async/await 绝对是一个目标,但首先我需要在没有错误的情况下编译所有内容,并且必须尽可能少地进行更改。
  • 我更新了 OP:名称 async 本身并不是问题的原因,retry 包和所有其他包也会发生同样的情况,即使是本地模块也是如此。

标签: node.js typescript typescript3.0


【解决方案1】:

这是可能的,但您仍然需要编辑这些文件。

这些方法中的任何一种都足够了。

  1. const ... = require() 替换为import ... = require()

    import async = require('async');
    ...
    
  2. 在文件顶部添加export {}

    export {};
    const async = require('async');
    ...
    

最初问题的原因是在 TS 中不同的文件是 not 模块,除非它们明确声明为模块,因此它们在相同的全局范围内编译/执行,这就是为什么 tsc 是向您报告 async 变量无法重新声明

来自documentation

在 TypeScript 中,就像在 ECMAScript 2015 中一样,任何包含顶级 importexport 的文件都被视为一个模块。相反,没有任何顶级 importexport 声明的文件被视为其内容在全局范围内可用的脚本(因此也可用于模块)。

【讨论】:

  • 当一个文件有一个import/export 语句时,它被认为是一个模块(而不是一个脚本)。脚本的处理方式与模块不同。在您的情况下,您需要模块。 const async = require('async'); 不认为是导入语句,所以必须添加真正的导入语句。
  • 为什么现有的module.exports 在这里不起作用?
  • @AlexanderZeitler 为什么module.exports 应该在 TypeScript 中工作
  • @AlexanderZeitler 对不起,我以为你知道。我已经更新了我的答案。
【解决方案2】:

这和this one是一样的问题。

为了被视为 ES 模块,文件应包含 importexport 语句,否则 TypeScript 编译器认为变量在全局范围内声明(即使在运行时不是这样) .

解决方法与链接问题相同,添加虚拟export {}。这可以通过正则表达式替换批量完成,但如果 CommonJS 、module.exports = ... 导出已在使用中,它们之间可能存在冲突。

使用 CommonJS require() 导入会导致无类型代码。所有主要的库都已经拥有 @types/... 或内置类型。现有的 NPM 包可以与代码库中的正则表达式匹配,以便批量安装相关的@types/... 包,const async = require('async') 等导入可以用import async from 'async' 批量替换。这需要设置esModuleInteropallowSyntheticDefaultImports 选项。

【讨论】:

    【解决方案3】:

    async 是受保护的关键字。当您使用 async/await 时,您可能会跳过“异步”包。如果您使用 ECMAScript 模块 (ESM) 正确地制作了 ES6+,那么您还将所有文件重命名为 *.mjs,例如 index.mjs。如果您有文件名 index.js,则通常假定它不是 ESM。您必须为所有 ES6 代码添加类型/接口,因此根据您的情况,一次制作所有代码可能不可行,这就是我以 ES2015+ ESM 表示法给出示例的原因。

    对于 TypeScript,您应该能够使用 ESM,因为我猜您想要更多最新的符号。为了在顶层使用异步,异步函数就是为此而存在的。 index.mjs 的示例代码,其中包括 ES2015+ 从 ES5/CommonJS *.js 导入,带有 module.exports 和 ESM 导入/导出,最后是动态导入:

    import { createRequireFromPath } from 'module'; // ESM import
    import { fileURLToPath } from 'url';
    const require = createRequireFromPath(fileURLToPath(import.meta.url));
    // const untypedAsync = require('async');
    
    class Index {
    
      constructor() {
        this._server = null;
        this.host = `localhost`;
        this.port = 8080;
      }
    
      set server(value) { this._server = value; }
      get server() { return this._server; }
    
      async start() {
        const http = await import(`http`); // dynamic import
        this.server = http.createServer(this.handleRequest);
        this.server.on(`error`, (err) => {
            console.error(`start error:`, err);
        });
        this.server.on(`clientError`, (err, socket) => {
            console.error(`start clientError:`, err);
            if (socket.writable) {
                return socket.end(`HTTP/1.1 400 Bad Request\r\n\r\n`);
            }
            socket.destroy();
        });
        this.server.on(`connection`, (socket) => {
          const arrival = new Date().toJSON();
          const ip = socket.remoteAddress;
          const port = socket.localPort;
          console.log(`Request from IP-Address ${ip} and source port ${port} at ${arrival}`);
        });
        this.server.listen(this.port, this.host, () => {
          console.log(`http server listening at ${this.host}:${this.port}`);
        });
      }
    
      handleRequest(req, res) {
        console.log(`url:`, req.url);
        res.setHeader(`Content-Type`, `application/json`);
        res.writeHead(200);
        res.end(JSON.stringify({ url: req.url }));
      }
    }
    
    export default Index; // ESM export
    export const randomName = new Index(); // Usage: import { randomName } from './index.mjs';
    
    async function main() {
      const index = new Index();
      const cjs = require(`./otherfile.js`); // ES5/CommonJS import
      console.log(`otherfile:`, cjs);
      // 'async' can be used by using: cjs.untypedAsync
      await index.start();
    }
    
    main();
    
    // in otherfile.js
    const untypedAsync = require('async');
    const test = {
      url: "url test",
      title: "title test",
    };
    module.exports = { test, untypedAsync }; // ES5/CommonJS export.
    

    但是,目前将 .mjs 与 typescript 一起使用存在一些问题。请查看仍然开放的相关打字稿问题:.mjs input files.mjs output files。您至少应该将 .ts 转换为 .mjs 以解决您的问题。脚本可能看起来像 (es6 to ts source):

    // in package.json
    "files": [ "dist" ],
    "main": "dist/index",
    "types": "dist/index.d.ts",
    "scripts": {
        "mjs": "tsc -d && mv dist/index.js dist/index.mjs",
        "cjs": "tsc -m commonjs",
        "start": "node --no-warnings --experimental-modules ./dist/index.mjs"
        "build": "npm run mjs && npm run cjs"
    },
    "devDependencies": {
        "typescript": "^3.2.2"
    }
    
    // in tsconfig.json
    "compilerOptions": {
        "module": "es2015",
        "target": "ES2017",
        "rootDir": "src",
        "outDir": "dist",
        "sourceMap": false,
        "strict": true
    }
    

    【讨论】:

      【解决方案4】:

      由于您要将大型项目迁移到 TypeScript,因此我建议您使用像这个包 (https://www.npmjs.com/package/javascript-to-typescript) 这样的工具,它可以自动完成一些工作。

      您可以编写一个脚本来打开项目中的每个文件,并按照@Styx 在他的回答中的建议在顶部添加export {}

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-02-22
        • 2017-03-14
        • 2020-11-13
        • 2020-08-14
        • 2010-12-09
        • 2015-02-09
        相关资源
        最近更新 更多