已开始将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
更改为.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
。这可能吗?
答案 0 :(得分:7)
有可能,但是您仍然必须编辑这些文件。
这些方法中的任何一种都足够。
将const ... = require()
替换为import ... = require()
:
import async = require('async');
...
将export {}
添加到文件顶部:
export {};
const async = require('async');
...
最初出现问题的原因是,在TS中,不同的文件不是 not 模块,除非它们明确声明为模块,因此它们在相同的全局范围内进行编译/执行,这就是{{1} }向您报告tsc
变量无法声明。
在TypeScript中,就像在ECMAScript 2015中一样,任何包含顶级
async
或import
的文件都被视为一个模块。相反,没有任何顶级export
或import
声明的文件将被视为脚本,其内容在全局范围内可用(因此也适用于模块)。
答案 1 :(得分:3)
这与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
选项。
答案 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 {}
。