Обновление: я обнаружил проблему в приведенном ниже подходе, когда машинописный текст автоматически генерирует файл объявления. Вы можете обнаружить, что ссылка на .ext.jsfiles не найдена в созданной папке dist. Это вызовет ошибку при его использовании и запуске компиляции машинописного текста.

Вместо этого вы должны использовать requirekeyword и предоставить соответствующее объявление этого как declare function require(path: string): any в верхней части .tsfiles. После этого просто экспортируйте его какexport = require('xxx.ext'). В остальном работа такая же.

Недавно я работал над внедрением машинописного текста в одну из библиотек, в которую я внес свой вклад, написанную на Vanilla Javascript. Старая кодовая база использовала веб-пакет для объединения всего и предоставления API и свойств в стиле UMD. Увидев тенденцию использования строго типизированного JS во многих местах, мы подумали, что было бы неплохо предоставить нашим потребителям некоторые файлы объявлений машинописного текста, которые, возможно, уже используют машинописный текст.

Я хочу написать краткое изложение того, что я сделал для работы по миграции. Мне потребовалось некоторое время, чтобы выяснить, какие есть варианты, как постепенно переносить крупномасштабную базу кода и убедиться, что все тесты по-прежнему действительны. Надеюсь, эта статья может немного помочь вам в этом.

На высоком уровне миграцию можно выполнить двумя способами: мы либо вручную поддерживаем файл декларации для нашей библиотеки и проверяем его в @types organization, либо позволяем компилятору машинописного текста автоматически создайте это для нас. Как бы хорошо это ни звучало для второго варианта, есть еще некоторые плюсы и минусы, которые нам нужно оценить, прежде чем двигаться дальше:

Ведение файла декларации вручную

  • Нет необходимости изменять какой-либо исходный код. Просто добавив один (или несколько) файлов объявлений для вашего пакета, вы сможете заявить всему миру, что ваша библиотека поддерживает машинописный текст! 😃
  • Полный контроль над оформлением файла декларации. Typescript предлагает использовать один из шаблонов для написания глобалов, модулей, функций и т. Д. 😐
  • К сожалению, вам придется делать это вручную, а также добавлять будущие обновления из ваших библиотек, которые вы хотите предоставить пользователям машинописного текста. 🤔

Пусть компилятор машинописного текста сделает всю работу за вас

  • Не нужно беспокоиться о написании файла декларации. Все файлы машинописного текста будут автоматически генерировать файл декларации с вашими скомпилированными кодами Javascript! 🍻
  • Хорошо работает с webpack или другими инструментами для связывания, такими как gulp. Вы, как обычно, получите связанный файл Javascript и несколько файлов объявлений для всего исходного кода машинописного текста. Вы даже можете поддерживать несколько версий Javascript! 😘
  • Однако переписать ваш любимый Javascript в машинописном тексте не так уж и легко, особенно когда вы имеете дело с крупномасштабной кодовой базой. 💔

Многие зрелые библиотеки, такие как подчеркивание и lodash, поддерживают машинописный текст по первому пути. Сравните, чтобы переписать все в машинописный текст, красивый, хорошо документированный файл декларации выглядит очень привлекательным для многих разработчиков, даже если он поддерживается вручную для каждой версии. С другой стороны, если машинописный текст - это то, на что вы хотите в конечном итоге перейти на саму библиотеку, второй вариант вам больше подойдет. Вполне возможно постепенно перенести исходный код в машинописный текст, а компилятор машинописного текста позаботится об остальной работе.

Кроме того, независимо от того, какой способ вы выберете, если вы вручную не опубликуете файл декларации в @types организации, вам нужно будет определить свойство types или typings в файле package.json. Более подробную информацию можно найти в документации по публикации машинописных текстов.

Я решил перейти на машинопись по многим веским причинам. Оставьте эти причины в стороне (которые прошли долгие споры между мной и моими коллегами, и, безусловно, стоит отдельного поста), ниже приведены шаги, которые я предпринял для постепенного переноса библиотеки Javascript.

  1. Оцените текущее состояние библиотеки. В моем случае библиотека следует за модулями CommonJS и предоставляет пакеты UMD.
  2. Прочтите, как это делают другие. Помимо официальных документов машинописного текста, я также прочитал Преобразование проекта JavaScript в машинописный текст, по одному файлу за раз. Это действительно полезно!
  3. Из входного файла для библиотеки выделили необходимые части, которые необходимо сначала перенести, и написали для них код машинописного текста. Мне также нужно написать фиктивные файлы объявлений для их зависимостей (подробности ниже).
  4. Просмотрите существующие тестовые примеры, чтобы убедиться, что все по-прежнему работает нормально.

Я уверен, что кроме шага 3 все очень просто. Стоит упомянуть одну вещь: вам необходимо настроить свои собственные сценарии сборки машинописного текста перед его использованием. Для этого я написал очень простую библиотеку под названием create-ts-library. В следующей демонстрационной библиотеке я предполагаю, что мы используем webpack для объединения и ts-loader для компиляции частей машинописного текста.

В нашей демонстрационной библиотеке файл записи myLib.js выглядит следующим образом (расширения файлов добавлены в require заявлениях для пояснения. Это необязательно):

Наша ближайшая цель - защитить типы для объекта конфигурации, которые требуются для ModuleA. В нашем случае нам нужно передать name строкового типа и type предварительно определенного типа перечисления. Как нам объявить типы только для этого объекта конфигурации в ModuleA, а все остальное оставить в Javascript?

Допустим, ModuleA выглядит так:

Объект config для ModuleA передается конструктору модуля. Мы хотели бы убедиться, что объект config имеет следующие типы:

Имейте в виду, что мы хотим иметь возможность перейти на машинописный текст и пользоваться автоматически созданными файлами объявлений постепенно. ModuleA выше может иметь только одну функцию-прототип, но на самом деле в нашей библиотеке их гораздо больше. Чтобы избежать всех некритичных изменений, нам нужно переписать ModuleA.js на ModuleA.ts, но оставить только конструктор:

Уловка здесь заключается в том, чтобы обернуть всю логику конструктора в новую функцию-прототип с именем initModuleA и поместить ее вместе со всеми другими функциями-прототипами в отдельный файл с именем ModuleAPrototype.js:

Вы можете спросить: «Вы все еще переносите библиотеку файл за файлом, не так ли?». Что ж, это правда, если взглянуть на файловую структуру выше. Файлы машинописного текста и файлы Javascript по существу должны быть разделены. Однако, перемещая только часть объекта или класса из исходного файла Javascript, вы можете постепенно удалить содержимое файла Javascript и добавить его в файл машинописного текста с типизированными определениями.

«Подождите, но у вас все еще есть огромная зависимость файла Javascript - ModuleAPrototype.js - осталась в ModuleA.ts. У вас также есть другие зависимости, например ModuleC. Машинописцу все это не обрадует, правда? "

Это тоже правда. Но для этого не сложно обмануть машинописный текст:

Да, я знаю, вам придется вручную добавить эти фиктивные файлы объявлений для длинного списка API или любых зависимостей, которые вам еще предстоит перенести. Но как только они появятся, эти файлы больше не потребуют внимания. В конечном итоге они будут удалены, когда все их содержимое будет перенесено в ModuleA.ts.

Теперь мы закончили перенос лишь крошечной части ModuleA на машинописный текст. Если вы запустите webpack сейчас, вы сможете получить связанный файл, который отлично работает во всех существующих тестах - webpack будет извлекать ModuleA.ts из входного файла, использовать ts-loader для компиляции частей машинописного текста и связывать выходные файлы Javascript, а также другие Все вместе коды Javascript.

Мы получили работу! или мы?

Кажется, что вся библиотека работает нормально сама по себе, но цель всего этого заключалась в том, чтобы помочь потребителям библиотеки, использующим машинописный текст, получить преимущества типизированных переменных. На этом этапе пакет был создан только с одним файлом объявлений, ModuleA.d.ts:

Когда потребитель импортирует нашу библиотеку, машинописный текст попытается найти файлы объявлений для входного файла myLab.d.ts. Поскольку такого файла не было, машинописный текст выдаст ошибки для потенциально any типа из вывода библиотеки. Мы могли бы определить свойство types в package.json файле как ModuleA.d.ts, но поскольку это не фактический файл записи, вы можете вызвать ненужную путаницу для своих потребителей. Не этого!

Что же нам тогда делать? Правильный способ - также перенести ваш входной файл в машинописный текст. Это может показаться слишком большим объемом накладных расходов, но вы можете использовать тот же метод, только перенеся зависимость на ModuleA, чтобы можно было распознать указанный выше файл ModuleA.d.ts:

И, конечно же, вам нужно пока вручную добавить myLibApi.d.ts:

После этого все готово к исходному коду библиотеки! Просто убедитесь, что в файле package.json указано правильное свойство types как myLab.d.ts. Вы увидите несколько файлов объявлений, созданных в выходном каталоге. Пока они подключены к myLab.d.ts, они будут работать нормально.

И последнее, но не менее важное: если у вас есть старые модульные тесты на исходную библиотеку Javascript (вам лучше иметь!), Теперь они, скорее всего, потерпят неудачу. Поскольку сейчас мы смешиваем Javascript и машинописный текст, средство выполнения тестов может не распознать эти новые файлы машинописного текста как есть.

К счастью, машинописный текст достаточно зрел, чтобы иметь множество официальных и неофициальных препроцессоров, таких как ts-jest или ts-node, которые могут помочь в этом случае. Просто следуйте документации на их конкретных страницах github, чтобы настроить, как средство запуска тестов может работать с машинописным текстом. Я использовал мокко для запуска модульных тестов в библиотеке, и все, что мне нужно сделать, это установить ts-node и добавить параметр CLI --require ts-node/register, чтобы он заработал. Довольно просто.

Конечным результатом вышеупомянутой работы является смешанная кодовая база с Javascript и машинописным текстом. С точки зрения библиотеки, мы четко определили типы для нашего критического объекта конфигурации ModuleA, и это решило нашу непосредственную проблему, заключающуюся в том, чтобы убедиться, что потребители знают, какие типы данных ожидаются. Если потребители хотят использовать больше общедоступных API, которые нам еще предстоит перенести (например, функция log в ModuleA), мы можем просто переместить ее с ModuleAPrototype.js на ModuleA.ts и определить ее типы по своему усмотрению.

Вот и все - переходите на машинописный текст, по одной функции за раз!

Вы можете проверить весь фиктивный пример на typescript-migration-demo. Прокомментируйте, пожалуйста, ниже или в репо, чтобы сообщить мне, что вы думаете об этом подходе. Спасибо!