从纯ES6将Node.js项目迁移到TypeScript

时间:2019-01-10 21:49:08

标签: node.js typescript typescript3.0

已开始将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 。这可能吗?

4 个答案:

答案 0 :(得分:7)

有可能,但是您仍然必须编辑这些文件。

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

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

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

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

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

来自documentation

  

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

答案 1 :(得分:3)

这与this one一样。

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

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

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

答案 2 :(得分:0)

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

对于TypeScript,您应该可以使用ESM,因为我想您需要更多最新的符号。为了在顶层使用异步,存在 async函数来执行此操作。 index.mjs的示例代码,包括从ES5 / CommonJS * .js导入ES2015 +,以及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
}

答案 3 :(得分:0)

由于您正在将一个大型项目迁移到TypeScript,所以我建议使用类似此软件包(https://www.npmjs.com/package/javascript-to-typescript)之类的工具,该工具可以使某些工作自动化。

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