将 Node.js 项目从普通 ES6 迁移到 TypeScript
Posted
技术标签:
【中文标题】将 Node.js 项目从普通 ES6 迁移到 TypeScript【英文标题】:Migrate Node.js project to TypeScript from plain ES6 【发布时间】:2019-06-05 20:05:33 【问题描述】:开始。
我做了什么:
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
更改为.ts
(node_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
包和所有其他包也会发生同样的情况,即使是本地模块也是如此。
【参考方案1】:
这是可能的,但您仍然需要编辑这些文件。
这些方法中的任何一种都足够了。
将const ... = require()
替换为import ... = require()
:
import async = require('async');
...
在文件顶部添加export
:
export ;
const async = require('async');
...
最初问题的原因是在 TS 中不同的文件是 not 模块,除非它们明确声明为模块,因此它们在相同的全局范围内编译/执行,这就是为什么 tsc
是向您报告 async
变量无法重新声明。
来自documentation:
在 TypeScript 中,就像在 ECMAScript 2015 中一样,任何包含***
import
或export
的文件都被视为一个模块。相反,没有任何***import
或export
声明的文件被视为其内容在全局范围内可用的脚本(因此也可用于模块)。
【讨论】:
当一个文件有一个import
/export
语句时,它被认为是一个模块(而不是一个脚本)。脚本的处理方式与模块不同。在您的情况下,您需要模块。 const async = require('async');
不认为是导入语句,所以必须添加真正的导入语句。
为什么现有的module.exports
在这里不起作用?
@AlexanderZeitler 为什么module.exports
应该在 TypeScript 中工作?
@AlexanderZeitler 对不起,我以为你知道。我已经更新了我的答案。【参考方案2】:
这和this one是一样的问题。
为了被视为 ES 模块,文件应包含 import
或 export
语句,否则 TypeScript 编译器认为变量在全局范围内声明(即使在运行时不是这样) .
解决方法与链接问题相同,添加虚拟export
。这可以通过正则表达式替换批量完成,但如果 CommonJS 、module.exports = ...
导出已在使用中,它们之间可能存在冲突。
使用 CommonJS require()
导入会导致无类型代码。所有主要的库都已经拥有 @types/...
或内置类型。现有的 NPM 包可以与代码库中的正则表达式匹配,以便批量安装相关的@types/...
包,const async = require('async')
等导入可以用import async from 'async'
批量替换。这需要设置esModuleInterop
和allowSyntheticDefaultImports
选项。
【讨论】:
【参考方案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
。
【讨论】:
以上是关于将 Node.js 项目从普通 ES6 迁移到 TypeScript的主要内容,如果未能解决你的问题,请参考以下文章
ES6模块,Node.js和Michael Jackson Solution
Node JS - json 到 jsongraph 转换器
KoaHub.js:使用ES6/7特性开发Node.js框架