Перенос проекта Node.js в TypeScript с простого ES6 - PullRequest
0 голосов
/ 11 января 2019

Начинается миграция проекта 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. Это возможно?

Ответы [ 4 ]

0 голосов
/ 16 января 2019

Поскольку вы переносите большой проект на TypeScript, я бы предложил использовать такой инструмент, как этот пакет (https://www.npmjs.com/package/javascript-to-typescript), который мог бы автоматизировать часть работы.

Вы можете написать сценарий для открытия каждого файла в проекте и добавить export {} вверху, как подсказал @Styx в своем ответе.

0 голосов
/ 13 января 2019

Это та же проблема, что и , эта .

Чтобы считаться модулем ES, файл должен содержать оператор import или export, в противном случае переменная считается объявленной в глобальной области видимости компилятором TypeScript (даже если это не так во время выполнения) .

Решение такое же, как в связанном вопросе, чтобы добавить пустышку export {}. Это может быть сделано в пакете с заменой регулярных выражений, но в случае, если CommonJS, module.exports = ... экспорт уже используется, между ними может возникнуть конфликт.

При использовании CommonJS require() импорт приводит к нетипизированному коду. Все основные библиотеки уже имеют согласно @types/... или встроенным типам. Существующие пакеты NPM могут быть сопоставлены с регулярным выражением из базы кода для установки соответствующих пакетов @types/... в пакетном режиме, а импорт, например const async = require('async'), может быть заменен в пакетном режиме import async from 'async'. Для этого необходимо установить опции esModuleInterop и allowSyntheticDefaultImports.

0 голосов
/ 13 января 2019

async является защищенным ключевым словом. Когда вы используете async / await, вы можете пропустить пакет 'async'. Если вы правильно сделали ES6 + с модулями ECMAScript (ESM) , вы также переименовали все свои файлы * .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 с машинописью в настоящее время имеет некоторые проблемы. Пожалуйста, посмотрите на связанные с этим проблемы с машинописью, которые все еще открыты: .mjs входные файлы и .mjs выходные файлы . Вы должны хотя бы перенести свои .ts в .mjs, чтобы решить ваши проблемы. Сценарии могут выглядеть следующим образом ( es6 для источника ):

// 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
}
0 голосов
/ 13 января 2019

Возможно, но вам все равно придется редактировать эти файлы.

Любой из этих методов будет достаточно.

  1. Заменить const ... = require() на import ... = require():

    import async = require('async');
    ...
    
  2. Добавьте export {} в начало файла:

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

Причиной первоначальной проблемы является то, что в TS разные файлы являются , а не модулями, если они явно не объявлены как модули, поэтому они компилируются / выполняются в одной и той же глобальной области видимости, и именно поэтому tsc сообщает Вы, что async переменная не может быть повторно объявлена ​​.

Из документации :

В TypeScript, как и в ECMAScript 2015, любой файл, содержащий import или export верхнего уровня, считается модулем. И наоборот, файл без каких-либо объявлений верхнего уровня import или export обрабатывается как сценарий, содержимое которого доступно в глобальной области (и, следовательно, также для модулей).

...